I'm writing a youtube video downloader. Let's use this video as an example:
https://www.youtube.com/watch?v=_UbDeqPdUek
When researching how to grab a YouTube video's url, I came across a snippet of code right here on stackoverflow:
(function() {
return ytplayer.config.args.adaptive_fmts
.split(',')
.map(item => item
.split('&')
.reduce((prev, curr) => (curr = curr.split('='),
Object.assign(prev, { [curr[0]]: decodeURIComponent(curr[1]) })
), {})
)
.reduce((prev, curr) => Object.assign(prev, {
[curr.quality_label || curr.type]: curr
}), {});
})();
Execute this in the chrome dev console on the youtube url above, and you'll see that it will spew forth an object with information, including video urls.
The 'url' property within the '720p' property, is the following:
https://r2---sn-5hne6nlk.googlevideo.com/videoplayback?mn=sn-5hne6nlk&ip=92.111.133.132&mm=31&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Ckeepalive%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csource%2Cexpire&itag=247&mime=video%2Fwebm&dur=250.800&id=o-AEgCrHyMCCJXgH39WmxsSwNs01bFa3BDVFEIzgAyrthL&mv=m&pl=18&mt=1500667926&requiressl=yes&ms=au&expire=1500689653&initcwndbps=1997500&keepalive=yes&key=yt6&ei=lWByWcWwI4Th1gKa2aDoCg&signature=596BF2603202A1BC60493C8263928FB365A14B2A.92D082149A543786BA5C29A547A8F78DE9346F95&source=youtube&clen=47937010&gir=yes&ipbits=0&lmt=1449652247373936
When I paste this url into a chrome tab, it starts playing the video in a barebones window. You can use this link to download the video. The problem is, the video has no sound!
When I use keepvid to download the video, it will have the following url on the 720p download button:
https://r17---sn-o097znll.googlevideo.com/videoplayback?lmt=1470986968011625&mt=1500670510&dur=250.891&itag=22&ipbits=0&ms=au&sparams=dur%2Cei%2Cid%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cexpire&ei=pWpyWZ75BZyf1gLf6rXQCA&id=o-AMPIR2-sd--NM8eDZyPKMqOu1B5qqSkBd1brw-H44ARx&pl=24&mn=sn-o097znll&mm=31&source=youtube&signature=6F5B6D2EFCF5CD600D9B12C984A19DBA58AC6A7E.CC2EE5E63809D4C9A07BB2C194328C7825015976&ratebypass=yes&mv=m&mime=video%2Fmp4&ip=159.253.144.86&requiressl=yes&key=yt6&expire=1500692229&title=Cubs+Meet+Adult+Tiger+For+The+First+Time+-+Tigers+About+The+House+-+BBC
The differences between both urls (and the query params) are small. But the fact is that keepvid's url works better than mine:
It spews up the file as a download, rather than playing it in the browser.
It has sound.
For all of your convenience, here are the url-decoded, formatted versions of these urls, with the parameters alphabetically ordered.
My url which I got from the js snippet above, with sound not working:
https://r2---sn-5hne6nlk.googlevideo.com/videoplayback
clen=47937010
dur=250.800
ei=lWByWcWwI4Th1gKa2aDoCg
expire=1500689653
gir=yes
id=o-AEgCrHyMCCJXgH39WmxsSwNs01bFa3BDVFEIzgAyrthL
initcwndbps=1997500
ip=92.111.133.132
ipbits=0
itag=247
keepalive=yes
key=yt6
lmt=1449652247373936
mime=video/webm
mm=31
mn=sn-5hne6nlk
ms=au
mt=1500667926
mv=m
pl=18
requiressl=yes
signature=596BF2603202A1BC60493C8263928FB365A14B2A.92D082149A543786BA5C29A547A8F78DE9346F95
source=youtube
sparams=clen,dur,ei,gir,id,initcwndbps,ip,ipbits,itag,keepalive,lmt,mime,mm,mn,ms,mv,pl,requiressl,source,expire
The url which I got from keepvid, with sound working:
https://r17---sn-o097znll.googlevideo.com/videoplayback
dur=250.891
ei=pWpyWZ75BZyf1gLf6rXQCA
expire=1500692229
id=o-AMPIR2-sd--NM8eDZyPKMqOu1B5qqSkBd1brw-H44ARx
ip=159.253.144.86
ipbits=0
itag=22
key=yt6
lmt=1470986968011625
mime=video/mp4
mm=31
mn=sn-o097znll
ms=au
mt=1500670510
mv=m
pl=24
ratebypass=yes
requiressl=yes
signature=6F5B6D2EFCF5CD600D9B12C984A19DBA58AC6A7E.CC2EE5E63809D4C9A07BB2C194328C7825015976
source=youtube
sparams=dur,ei,id,ip,ipbits,itag,lmt,mime,mm,mn,ms,mv,pl,ratebypass,requiressl,source,expire
title=Cubs Meet Adult Tiger For The First Time - Tigers About The House - BBC
As you can see, the urls/parameters are very similar. But how does keepvid manage to produce a file url that has the sound working, while my js snippet craps out on me without audio?
The urls have a signature as a parameter. This is likely the signature for (some of the) query parameters itself. Changing something small in a parameter (delete it, or change its value), results in the url getting access denied. This makes it hard to experiment with it.
The js snippet simply accesses an online variable and digs the video info from it. But what's the snippet missing that's causing its urls to refer to soundless videos?
Can anybody help me to programmatically obtain a url just like keepvid's, which will result in a downloaded file with working audio?
[Update]
I've just learned you can grab video info like such:
http://youtube.com/get_video_info?video_id=_UbDeqPdUek
It yields a big file with loads of info, that I haven't figured out how to parse yet.
I must be on the right path, because this is how youtube-dl is doing it. And that program works flawlessly.
I suppose I could simply reverse engineer youtube-dl (costs time). Or even call it from my C# program using Process (makes me dependent on it).
But that still leaves me with the question of how a person would figure this out on their own, if no help was available?
Youtube has 2 type of streams - multiplexed and adaptive. First one has both video and sound, second one only has either of two. Since they are being phased out, multiplexed streams are typically of much lower quality than their adaptive counterparts. What KeepVideo does is present you with the old multiplexed format.
Besides that, on a big portion of videos Youtube uses signature ciphering which adds another pretty massive step to retrieving stream URLs. I don't recommend doing this on your own.
You can check out YoutubeExplode which is a library that lets you query and download videos. You can also check out this sample project which downloads and multiplexes videos using YoutubeExplode and ffmpeg.
But that still leaves me with the question of how a person would figure this out on their own, if no help was available?
Open Chrome developer console and keep inspecting the Network tab until you start noticing patterns.
Related
I'm trying to display a continuous video stream (live-stream) in a browser.
Description:
My client reported a video stream doesn't work in the Chrome browser. I thought it will be an easy case, I even tried to write a demo, to prove streaming should be available with just HTML5 native video tag:
https://github.com/mishaszu/streaming-video-demo
No problems with random video but:
the particular video stream on client-side doesn't work.
With html code:
<video id="video-block" width="320" height="200" autoplay>
<source src="url/to/my/video" type="video/mp4">
</video>
it shows loader for a while and dies.
What I know about the stream:
1. Codec used: H264-MPEG-4 AVC (part 10) (avc1)
2. It's a live stream, not a file, so I can't use command like MP4Box from a terminal with it
3. Because it's live stream it probably doesn't have "end of file"
4. I know it's not broken because VLC is able to display it
5. I tried native HTML 5 video tag with all Media type strings (just in case to check all codecs for mp4)
As I mentioned trying different mime types didn't help, I also tried to use MediaSource but I am really not sure how to use it with a live stream, as all information I found made assumptions:
a) waiting for resolve promise and then appends buffer
b) adding the event listener for updateend to appends buffer
I think in the case of a live stream it won't work.
Conclusion:
I found a lot of information about how a streamed file might contain metadata (at the beginning of the file or at the end)... and I ended up with a conclusion that maybe I do not fully understand what's going on.
Questions:
What's the proper way to handle the mp4 live stream?
If a native HTML video tag should support the live stream, how to debug it?
I thought that maybe I should look for something like HLS but for mp4 format?
I've went through the same - I needed to mux an incoming live stream from rtsp to HTML5 video, and sadly this may become non-trivial.
So, for a live stream you need a fragmented mp4 (check this SO question if you do not know what that is:). The is the isobmff specification, which sets rules on what boxes should be present in the stream. From my experience though browsers have their own quirks (had to debug chrome/firefox to find them) when it comes to a live stream. Chrome has chrome://media-internals/ tab, which shows the errors for all loaded players - this can help debugging as well.
So my shortlist to solve this would be:
1) If you say that VLC plays the stream, open the Messages window in VLC ( Tools -> Messages ), set severity to debug, and you should see the mp4 box info in there as the stream comes in, verify that moof boxes are present
2a) Load the stream in chrome, open chrome://media-internals/ in a new tab and inspect errors
2b) Chrome uses ffmpeg underneath, so you could try playing the stream with ffplay as well and check for any errors.
2c) You are actually incorrect about mp4box - you could simply load a number of starting bytes from the stream, save to a file and use mp4box or other tools on that (at worst it should complain about some corrupted boxes at the end if you cut a box short)
If none of 2a/2b/2c provide any relevant error info that you can fix yourself, update the question with the outputs from these, so that others have more info.
I'm building a video player in node.js with nw.js. It will run offline.
One of the features I'd like to incorporate on this application is the possibility of to play the video dragging and drop it into a "box".
The restriction I'm facing on this implementation is the necessity of encode the video in order to play it, using, for example, the function readAsDataURL(). As discussed in this post, it is not possible to get the fullpath of a file.
"Upload" the entire video, for me, doesn't make sense as long it is already stored in the user's hd.
If he/she try to play The Big Bang Theory (about 20 minutes), it won't be problem to wait 2 or 3 minutes, differently of try to watch The Lord of Rings.
Is there good workaround to deal with this problem?
I appreciate any help.
UPDATE:
I was thinking about the copy and paste of file in a field, as long as with this action is possible to get its URL. But it is not the best thing in terms of user experience...
I've managed. I've changed readAsDataURL() for createObjectURL().
For the sake of reference, my code
var video = document.createElement("video");
video.controls = true;
document.body.appendChild(video);
video.src = (window.URL||window.webkitURL).createObjectURL(file);
video.play();
Now, the The Lord of Rings marathon of my users is saved.
I'm building a simple Javascript jukebox using the latest SoundManager2 for audio playback, with local MP3 files being the source. I've got file loading and playing sorted, and at the moment I'm trying to get access to the ID3 info of these MP3 files, but the onid3() callback is not firing. I'm using Flash and have verified that ID3 info is present in the files. Below is my implementation of onid3():
function playNextSongInQueue()
{
// Get the first element of the songQueue array
var nextSongInQueue = songQueue.shift();
// Start playback from the queue
var jukeboxTune = soundManager.createSound({
id: 'currentTune',
url: 'audio/' + nextSongInQueue.name,
onload: function() {
this.play();
},
onid3: function() {
alert('ID3 present!');
},
onfinish: function() {
this.destruct(); // Destroy this sound on finish
songFinish(); // Run the songFinish() function, so decide what to do next
}
});
jukeboxTune.load();
//jukeboxTune.play(); // The jukebox running!
songPlaying = true; // Set songPlaying flag
updateSongQueueDisplay(); // Refresh the song queue display (for debug)
return nextSongInQueue.name;
}
The other callbacks work fine, but the onid3() alert never comes up. I even separated the load and play portions of audio playback to see if that helped. SoundManager spots that onid3() is there because it switches usePolicyFile to true - seeing as the MP3s are local I am assuming I don't need to worry about the cross-domain XML file.
Can anybody shed light on why this isn't working? I've scoured Google looking for implementations that work but have come up with nothing helpful. I've seen Jacob Seidelin's pure Javascript workaround but would rather stick with SoundManager if possible, and would rather not use a PHP solution.
Thanks,
Adam
This problem is probably too esoteric for any solid answers, so I decided to investigate possible Javascript solutions outside the SM2 library.
I started with Nihilogic's library for reading ID3v1 tags (at http://blog.nihilogic.dk/2008/08/reading-id3-tags-with-javascript.html), but moved to antimatter15's js-id3v2 library (https://github.com/antimatter15/js-id3v2) as it can read ID3v2 tags. Adapting code from the provided example I have managed to successfully parse the main tags required when the MP3s are loaded via the <input> control.
For local files, i speak of "user local files" (not "server" local files) i get some success with id3v2.js
To get ID3, SM2 need a cross domain on the mp3 host, if it's another domain.
Plus i have encountered difficulties with Soundcloud as they redirect MP3 to dynamic Amazon S3 storage... so i have to do a PHP script to guest final URL and then SM2 can get proper crossdomain.xml (Check https://getsatisfaction.com/schillmania/topics/displaying_waveformdata_of_soundcloud_hosted_track_prompts_securityerror_error_2122 )
The problem is both S3 links and local user files (blob) do have a short expiration delay.
Good luck !
Users run my HTML files locally, straight from a CD.
I want to allow them to choose a bunch of videos and create a playlist on the fly.
This works very well if I run a web server but when I run the HTML itself it fails.
The player is created (using swfobject) and all my other code runs but playerReady never fires so I can never get the current play list to add to it.
Any ideas on how I can fix this or, more likely, work around it?
If the player is created, but you're not getting a playerReady, one of two things could be happening.
There's another playerReady on the page that's catching your playerReady. Make sure that there's just one playerReady on the page.
You haven't enabled JavaScript access for Flash. The code for that would look like this:
SWFObject:
var so = new SWFObject('player.swf','ply','470','320','9','#000000');
so.addParam('allowfullscreen','true');
so.addParam('allowscriptaccess','always');
so.addParam('wmode','opaque');
so.addVariable('file','video.flv');
so.write('mediaspace');
I should also note that there are some additional Flash security restrictions because you're accessing the player from disk. Namely, you can't access both a disk source and a network source (the Internet) simultaneously.
Best,
Zach
Developer, LongTail Video
I have a dashboard web-app that I want to play an alert sound if its having problems connecting. The site's ajax code will poll for data and throttle down its refresh rate if it can't connect. Once the server comes back up, the site will continue working.
In the mean time I would like a sound to play each time it can't connect (so I know to check the server). Here is that code. This code works.
var error_audio = new Audio("audio/"+settings.refresh.error_audio);
error_audio.load();
//this gets called when there is a connection error.
function onConnectionError() {
error_audio.play();
}
However the 2nd time through the function the audio doesn't play. Digging around in Chrome's debugger the 'played' attribute in the audio element gets set to true. Setting it to false has no results. Any ideas?
I encountered this just today, after more searching I found that you must set the source property on the audio element again to get it to restart. Don't worry, no network activity occurs, and the operation is heavily optimized.
var error_audio = new Audio("audio/"+settings.refresh.error_audio);
error_audio.load();
//this gets called when there is a connection error.
function onConnectionError() {
error_audio.src = "audio/"+settings.refresh.error_audio;
error_audio.play();
}
This behavior is expressed in chrome 21. FF doesn't seem to mind setting the src twice either!
Try setting error_audio.currentTime to 0 before playing it. Maybe it doesn't automatically go back to the beginning
You need to implement the Content-Range response headers, since Chrome requests the file in multiple parts via the Range HTTP header.
See here: HTML5 <audio> Safari live broadcast vs not
Once that has been implemented, both the play() function and setting the currentTime property should work.
Q: I’VE GOT AN AUDIOBUFFERSOURCENODE, THAT I JUST PLAYED BACK WITH NOTEON(), AND I WANT TO PLAY IT AGAIN, BUT NOTEON() DOESN’T DO ANYTHING! HELP!
A: Once a source node has finished playing back, it can’t play back more. To play back the underlying buffer again, you should create a new AudioBufferSourceNode and call noteOn().
Though re-creating the source node may feel inefficient, source nodes are heavily optimized for this pattern. Plus, if you keep a handle to the AudioBuffer, you don't need to make another request to the asset to play the same sound again. If you find yourself needing to repeat this pattern, encapsulate playback with a simple helper function like playSound(buffer).
Q: WHEN PLAYING BACK A SOUND, WHY DO YOU NEED TO MAKE A NEW SOURCE NODE EVERY TIME?
A: The idea of this architecture is to decouple audio asset from playback state. Taking a record player analogy, buffers are analogous to records and sources to play-heads. Because many applications involve multiple versions of the same buffer playing simultaneously, this pattern is essential.
source:
http://updates.html5rocks.com/2012/01/Web-Audio-FAQ
You need to pause the audio just before its end and change the current playing time to zero, then play it.
Javascript/Jquery to control HTML5 audio elements - check this link - explains How to handle/control the HTML5 audio elements?. It may help you!
Chrome/Safari have fixed this issue in newer versions of the browser and the above code now works as expected. I am not sure the precise version it was fixed in.