I can't find any informations to check if a YouTube channel is actually streaming or not.
With Twitch you just need the channel name, and with the API you can check if there is a live or not.
I don't want to use OAuth, normally a public API key is enough. Like checking the videos of a channel I want to know if the channel is streaming.
You can do this by using search.list and specifying the channel ID, setting the type to video, and setting eventType to live.
For example, when I searched for:
https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=UCXswCcAMb5bvEUIDEzXFGYg&type=video&eventType=live&key=[API_KEY]
I got the following:
{
"kind": "youtube#searchListResponse",
"etag": "\"sGDdEsjSJ_SnACpEvVQ6MtTzkrI/gE5P_aKHWIIc6YSpRcOE57lf9oE\"",
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 5
},
"items": [
{
"kind": "youtube#searchResult",
"etag": "\"sGDdEsjSJ_SnACpEvVQ6MtTzkrI/H-6Tm7-JewZC0-CW4ALwOiq9wjs\"",
"id": {
"kind": "youtube#video",
"videoId": "W4HL6h-ZSws"
},
"snippet": {
"publishedAt": "2015-09-08T11:46:23.000Z",
"channelId": "UCXswCcAMb5bvEUIDEzXFGYg",
"title": "Borussia Dortmund vs St. Pauli 1-0 Live Stream",
"description": "Borussia Dortmund vs St. Pauli Live Stream Friendly Match.",
"thumbnails": {
"default": {
"url": "https://i.ytimg.com/vi/W4HL6h-ZSws/default.jpg"
},
"medium": {
"url": "https://i.ytimg.com/vi/W4HL6h-ZSws/mqdefault.jpg"
},
"high": {
"url": "https://i.ytimg.com/vi/W4HL6h-ZSws/hqdefault.jpg"
}
},
"channelTitle": "",
"liveBroadcastContent": "live"
}
}
]
}
The search-method (https://www.googleapis.com/youtube/v3/search) is awfully expensive to use though. It costs 100 quota units (https://developers.google.com/youtube/v3/determine_quota_cost) out of the 10,000 you have by default.
This means you only get 100 requests per day which is terrible.
You could request an increase in the quota but that seems like brute forcing the the problem.
Is there really no other simpler method?
I know this is old, but I figured it out myself with PHP.
$API_KEY = 'your api3 key';
$ChannelID = 'the users channel id';
$channelInfo = 'https://www.googleapis.com/youtube/v3/search?part=snippet&channelId='.$ChannelID.'&type=video&eventType=live&key='.$API_KEY;
$extractInfo = file_get_contents($channelInfo);
$extractInfo = str_replace('},]',"}]",$extractInfo);
$showInfo = json_decode($extractInfo, true);
if($showInfo['pageInfo']['totalResults'] === 0){
echo 'Users channel is Offline';
} else {
echo 'Users channel is LIVE!';
}
Guys I found better way to do this. Yes, it requires you to make GET requests to a YouTube page and parse HTML, but it will work with newer versions + works with consent + works with captcha (most likely, 90%)
All you need to do is make a request to https://youtube.com/channel/[CHANNELID]/live and check the href attribute of the <link rel="canonical" /> tag.
For example,
<link rel="canonical" href="https://www.youtube.com/channel/UC4cueEAH9Oq94E1ynBiVJhw">
means there is no livestream, while
<link rel="canonical" href="https://www.youtube.com/watch?v=SR9w_ofpqkU">
means there is a stream, and you can even fetch its data by videoid.
Since canonical URL is very important for SEO and redirect does not work in GET or HEAD requests anymore, I recommend using my method.
Also here is the simple script I use:
import { parse } from 'node-html-parser'
import fetch from 'node-fetch'
const channelID = process.argv[2] // process.argv is array of arguments passed in console
const response = await fetch(`https://youtube.com/channel/${channelID}/live`)
const text = await response.text()
const html = parse(text)
const canonicalURLTag = html.querySelector('link[rel=canonical]')
const canonicalURL = canonicalURLTag.getAttribute('href')
const isStreaming = canonicalURL.includes('/watch?v=')
console.log(isStreaming)
Then run npm init -y && npm i node-html-parser node-fetch to create project in working directory and install dependencies
Then run node isStreaming.js UC4cueEAH9Oq94E1ynBiVJhw and it will print true/false (400-600 ms per one execution)
It does require you to depend on node-html-parser and node-fetch, but you can make requests with the built-in HTTP library (which sucks) and rewrite this to use regex. (Do not parse HTML with regex.)
I was also struggling with API limits. The most reliable and cheapest way I've found was simply a HEAD request to https://www.youtube.com/channel/CHANNEL_ID/live. If the channel is live then it will auto load the stream. If not then it will load the channels videos feed. You can simply check the Content-Length header size to determine which. If live the size is almost 2x when NOT live.
And depending on your region you might need to accept the cookies consent page. Just send your request with cookies={ "CONSENT": "YES+cb.20210420-15-p1.en-GB+FX+634" }.
if you point streamlink at a https://www.youtube.com/channel/CHANNEL_ID/live link, it will tell you if it is live or not
e.g. lofi beats is usually live,
$ streamlink "https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow/live"
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/channel/UCSJ4gkVC6NrvII8umztf0Ow/live
Available streams: 144p (worst), 240p, 360p, 480p, 720p, 1080p (best)
whereas MKBHD is not
$ streamlink "https://www.youtube.com/c/mkbhd/live"
[cli][info] Found matching plugin youtube for URL https://www.youtube.com/c/mkbhd/live
error: Could not find a video on this page
The easisest way that I have found to this has been scraping the site. This can be done by finding this:
<link rel="canonical" href="linkToActualYTLiveVideoPage">
as in Vitya's answer.
This is my simple Python code using bs4:
import requests
from bs4 import BeautifulSoup
def is_liveYT():
channel_url = "https://www.youtube.com/c/LofiGirl/live"
page = requests.get(channel_url, cookies={'CONSENT': 'YES+42'})
soup = BeautifulSoup(page.content, "html.parser")
live = soup.find("link", {"rel": "canonical"})
if live:
print("Streaming")
else:
print("Not Streaming")
if __name__ == "__main__":
is_liveYT()
It is pretty weird, honestly, that YouTube doesn't have a simple way to do this through the API, although this is probably easier.
I found the answer by #VityaSchel to be quite useful, but it doesn't distinguish between channels which have a live broadcast scheduled, and those which are broadcasting live now.
To distinguish between scheduled and live, I have extended his code to access the YouTube Data API to find the live streaming details:
import { parse } from 'node-html-parser'
import fetch from 'node-fetch'
const youtubeAPIkey = 'YOUR_YOUTUBE_API_KEY'
const youtubeURLbase = 'https://www.googleapis.com/youtube/v3/videos?key=' + youtubeAPIkey + '&part=liveStreamingDetails,snippet&id='
const c = {cid: process.argv[2]} // process.argv is array of arguments passed in console
const response = await fetch(`https://youtube.com/channel/${c.cid}/live`)
const text = await response.text()
const html = parse(text)
const canonicalURLTag = html.querySelector('link[rel=canonical]')
const canonicalURL = canonicalURLTag.getAttribute('href')
c.live = false
c.configured = canonicalURL.includes('/watch?v=')
if (!c.configured) process.exit()
c.vid = canonicalURL.match(/(?<==).*/)[0]
const data = await fetch(youtubeURLbase + c.vid).then(response => response.json())
if (data.error) {
console.error(data)
process.exit(1)
}
const i = data.items.pop() // pop() grabs the last item
c.title = i.snippet.title
c.thumbnail = i.snippet.thumbnails.standard.url
c.scheduledStartTime = i.liveStreamingDetails.scheduledStartTime
c.live = i.liveStreamingDetails.hasOwnProperty('actualStartTime')
if (c.live) {
c.actualStartTime = i.liveStreamingDetails.actualStartTime
}
console.log(c)
Sample output from the above:
% node index.js UCNlfGuzOAKM1sycPuM_QTHg
{
cid: 'UCNlfGuzOAKM1sycPuM_QTHg',
live: true,
configured: true,
vid: '8yRgYiNH39E',
title: '🔴 Deep Focus 24/7 - Ambient Music For Studying, Concentration, Work And Meditation',
thumbnail: 'https://i.ytimg.com/vi/8yRgYiNH39E/sddefault_live.jpg',
scheduledStartTime: '2022-05-23T01:25:00Z',
actualStartTime: '2022-05-23T01:30:22Z'
}
Every YouTube channel as a permanent livestream, even if the channel is currently not actively livestreaming. In the liveStream resource, you can find a boolean named isDefaultStream.
But where can we get this video (livestream) id? Go to https://www.youtube.com/user/CHANNEL_ID/live, right click on the stream and copy the video URL.
You can now make a GET request to
https://youtube.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=[VIDEO_ID]&key=[API_KEY] (this request has a quota cost of 1 unit, see here)
This will be the result if the stream is currently active/online.
{
"kind": "",
"etag": "",
"items": [
{
"kind": "",
"etag": "",
"id": "",
"liveStreamingDetails": {
"actualStartTime": "",
"scheduledStartTime": "",
"concurrentViewers": "",
"activeLiveChatId": ""
}
}
],
"pageInfo": {
"totalResults": 1,
"resultsPerPage": 1
}
}
If the stream is currently offline, the property concurrentViewers will not exist. In other words, the only difference between an online and offline livestream is that concurrentViewers is present or not present. With this information, you can check, if the channel is currently streaming or not (at least for his default stream).
I found youtube API to be very restrictive given the cost of search operation. Web scraping with aiohttp and beautifulsoup was not an option since the better indicators required javascript support. Hence I turned to selenium. I looked for the css selector
#info-text
and then search for the string Started streaming or with watching now in it.
You can run a small API on heroku with flask as well.
I am attempting to use the Wikipedia API to retrieve article titles and snippets of the article's text. But when I try to access those properties, I am getting the error "Cannot read property of undefined."
Here is my JSON response:
{
"batchcomplete": "",
"continue": {
"gsroffset": 10,
"continue": "gsroffset||"
},
"query": {
"pages": {
"13834": {
"pageid": 13834,
"ns": 0,
"title": "\"Hello, World!\" program",
"index": 6,
"extract": "<p>A <b>\"Hello, World!\" program</b> is a computer program that outputs or displays \"Hello, World!\" to a user. Being a very simple program in most programming languages, it is often used to illustrate the</p>..."
},
"6710844": {
"pageid": 6710844,
"ns": 0,
"title": "Hello",
"index": 1,
"extract": "<p><i><b>Hello</b></i> is a salutation or greeting in the English language. It is first attested in writing from 1826.</p>..."
},
"1122016": {
"pageid": 1122016,
"ns": 0,
"title": "Hello! (magazine)",
"index": 7,
"extract": "<p><i><b>Hello</b></i> (stylised as <i><b>HELLO!</b></i>) is a weekly magazine specialising in celebrity news and human-interest stories, published in the United Kingdom since 1988. It is the United Kingdom</p>..."
}
}
}
}
I have tried a couple different ways of writing the code. For example, this works (logs the pages as an object in the console):
console.log(response.query.pages);
But this returns the error I wrote above ("Cannot read property of undefined"):
console.log(response.query.pages[0].title);
Any suggestions on how to access the attributes "title" and "extract" would be appreciated. Thanks.
That's because pages is not an array; it's an object where the keys are the ids. So you need to do:
console.log(response.query.pages[1122016].title);
This will work. If you want the "first" page, for instance, then
let pages = response.query.pages;
console.log(pages[Object.keys(pages)[0]].title);
Note that I'm not sure if the order of the keys in JS objects is guaranteed.
If you want to iterate over the pages, do
let pages = response.query.pages;
Object.keys(pages).forEach(id => {
let page = pages[id];
console.log(page.title, page.foo);
});
Special Case: Working with Asynchronous Calls
Howdy fellow devs,
If you're checking out this thread because you're working with a framework like React, or some other framework that has you using a development server (e.g. using npm start or something similar), your dev server may be crashing when you try to do something like console.log(response.foo.bar) before it refreshes on data reload.
Specifically, my dev server was crashing with the Cannot read property 'bar' of undefined type message, and I was like, "what the heck is going on here!?". Solution: put that baby in a try/catch block:
try {
console.log(rate['bar'].rate)
} catch (error) {
console.log(error)
}
Why? If your App has a default state (even an empty array, for example), then it tries to console.log the response before the data has been received from the remote source, it will successfully log your empty state, but if you try to reference parts of the object you're expecting to receive from the remote source in your console.log or whatever else, the dev server will be trying to reference something not there on initial load and crash before it has a chance to reference it when it's actually received from the remote source via API or whatever.
Hope this helps someone!
I'm not sure which language you're using to parse the JSON (looks like Javascript from console.log?) but the issue is that query.pages is a dictionary, not an array, so it can't be iterated by index, only by key.
So you want something like (pseudocode):
for (key in response.query.keys)
{
console.log(response.query[key].title);
}
I need all google reviews for particular location but I am unable to use business api from google. Here is url for get request
https://mybusiness.googleapis.com/v3/accounts/account_name/locations/location_name/reviews
Now my question is what is the value for param account_name and location_name
How can I get that.
Please answer with sample location example
I think first of all you need to white list your google my business api for whatever project you are working on in your project as its private api. Google my business api will work on the locations associated with your account so make sure you verified the LOCATIONS from any account you know. Then you can try out the api call you mentioned in OAuthplayground.
Follow steps mentioned in below documentation URL to set it up:
https://developers.google.com/my-business/content/prereqs
After the setup and etc you will automatically understand the account id and location id.
Also few more urls you can go to understand it better.
https://console.developers.google.com (here you will setup your project)
https://business.google.com/manage (here you will add/can see the locations - for which you need reviews)
https://developers.google.com/my-business/content/basic-setup (Steps after completing the prereq)
https://developers.google.com/oauthplayground (You will test the my
business api here after approval)
When you make a request to https://mybusiness.googleapis.com/v3/accounts it gives you a list of accounts. On those accounts they have a field called name. That field is accounts/account_name.
{
"state": {
"status": "UNVERIFIED"
},
"type": "PERSONAL",
"name": "accounts/1337",
"accountName": "example"
}
When you make a request to https://mybusiness.googleapis.com/v3/accounts/account_name/locations it gives you a list of locations. On those locations they have a field called name. That field is accounts/account_name/locations/location_name.
{
"locations": [
{
"languageCode": "en",
"openInfo": {
"status": "OPEN",
"canReopen": true
},
"name": "accounts/1337/locations/13161337",
...
}
I am trying to perform this request:
GET https://www.googleapis.com/youtube/analytics/v1/reports?ids=contentOwner%3D%myId&start-date=2014-05-01&end-date=2014-06-30&metrics=views&dimensions=province&filters=channel%3D%chanId%3Bcountry%3DUS&sort=- estimatedMinutesWatched&key={YOUR_API_KEY}
My aim is to generate a content owner report for a given channel that we manage that will return views and earnings information for only US states.
The above returns this error:
400 Bad Request
- Show headers -
{
"error": {
"errors": [
{
"domain": "global",
"reason": "badRequest",
"message": "Could not parse content (N/A) of field parameters.filters."
}
],
"code": 400,
"message": "Could not parse content (N/A) of field parameters.filters."
}
}
I am using the documentation on the API Explorer page and my request appears to be correct.
Any help would be greatly appreciated.
Your problem is you use filters with "=" instead of "==" like "channel==ID"
After that, you will have another problem. I tried same query and it worked with adding estimatedMinutesWatched into views. To use that as sort order, you need to use it in views or dimensions.
Here's my request:
GET
https://www.googleapis.com/youtube/analytics/v1/reports?ids=contentOwner%3D%3DCONTOWNER&start-date=2014-05-01&end-date=2014-06-30&metrics=views%2CestimatedMinutesWatched&dimensions=province&filters=channel%3D%3DUCBLABLA%3Bcountry%3D%3DUS&sort=-estimatedMinutesWatched&key={YOUR_API_KEY}
Make sure your request fits one of https://developers.google.com/youtube/analytics/sample-requests#Content_Owner_Reports
and try it in https://developers.google.com/apis-explorer/#p/youtubeAnalytics/v1/youtubeAnalytics.reports.query
I am trying to get the duration of a youtube video via search
var request = gapi.client.youtube.search.list({
q: q,
type : "video",
maxResults : 10,
part: 'snippet,contentDetails'
});
as an additional part parameter I added contentDetails in order to get the duration as you can see in their doc.
https://developers.google.com/youtube/v3/getting-started
Now comes the silly part. The response I get is the following:
[
{
"error": {
"code": -32602,
"message": "contentDetails",
"data": [
{
"domain": "youtube.part",
"reason": "unknownPart",
"message": "contentDetails",
"locationType": "parameter",
"location": "part"
}
]
},
"id": "gapiRpc"
}
]
"As such, the part parameter requires you to select the resource components that your application actually uses"
Thats what I did and now they dont know their own parameter anylonger?
So I was wondering how can I retrieve the duration order contentDetails in general?
best
phil
As written in the document, for search->list call, part can only take value "snippet".
Depending on the kind of the response, as a secondary call, you can do one of videos->list, playlists->list, channels->list with the id and part=snippet,contentDetails to get more details of each result item.