I've been running into issues with Safari and the <video> element in combination with shorter high-quality videos. For a showcase website I'm loading 12-15 second .mp4 and .webm files into a <video> container using the <source> element. When trying to play the video in Chrome it works flawlessly and the video start playing almost instantaneously, but Safari appears to want to load the video completely before starting playback.
I have looked into loading the video directly through the src attribute of <video> and have also added the preload="auto" attribute in an attempt to force immediate playback.
I've set up an example using one of the videos we use on the websites, which is around 8 MB and 12 seconds long: https://jsfiddle.net/n1eac46v/
var video = document.getElementById('video');
var source = document.createElement('source');
source.src = "foo.mp4";
source.type = "video/mp4";
video.appendChild(source);
video.on("canplay canplaythrough", video.play);
As you can see I'm also listening for the canplay and the canplaythrough events, but even that doesn't appear to help. I've been looking all over the place for days on end but am running out of options now.
I am not an OS X user so I have no experience with Safari, however I've had the chance to debug mp4 playback in Chrome. Chrome has a buffering window that it tries to fill until it begins actual playback. Safari may have a bigger window. Also since in normal cases all the tables that contain sample positions, durations etc. are at the end of the file browser may want to read them to begin playback. A smart browser can dispatch a read with a byte range header to get only the end of the file and then seek the actual video data, but again I have no idea what safari does.
What you can try is to use MP4Box on your video files to move the meta data and all tables to the beginning, that could help.
As mentioned by OP in the comments using FFmpeg with -movflags faststart (see FFmpeg MP4 options) can also be used to achieve this.
Related
Suppose I have a page with a video tag in it. I can connect the audio output of that video to a WebaAudio node like this:
const source = audioContext.createMediaElementSource(video);
source.connect(myNode);
Then I can take that node and connect it to audioContext.destination to hear the result. The problem is that I also seem to hear the output of the original video, before it gets processed by my audio node. I want to basically channel the video output exclusively through my node. How can I do this?
Note that I want to leave the audio controls on the video element alone, that is, I can't use a solution involving video.muted = true or something, since the user will see the volume meter at zero in the UI, which I don't want. I want to let the user set the video volume with the controls, then take the output of that and pipe it into my audio node.
It turns out this is a Firefox bug, or more specifically a bug in the Ubuntu package build of Firefox.
Ubuntu Firefox package issue page: https://bugs.launchpad.net/ubuntu/+source/firefox/+bug/1852645
SITUATION:
It works fine on Chrome. But there is a small gap every time the "Waterfall 1" sound loops, only on Safari.
WHAT I TRIED SO FAR:
With the HTML 5 audio element:
audio.addEventListener('timeupdate', function(){
var buffer = 0.44;
if(this.currentTime > this.duration - buffer){
this.currentTime = 0
this.play()
}}, false);
Gapless.js https://github.com/regosen/Gapless-5
Howler.js https://github.com/goldfire/howler.js
Web Audio API alone: How to seamlessly loop sound with web audio api
QUESTION:
How can I get rid of the gap in the audio loop in Safari ?
I had the same issue and I resolved it by using a file format other than mp3.
I would also highly recommend using howler.js!
Here is a detailed article that might help: https://www.kevssite.com/seamless-audio-looping/
While looking for a fix to this I learnt that part of the ‘pause’ problem is caused by using mp3 as the audio file format. Apparently there is a bug (or maybe its a feature!) in the mp3 spec that adds a few ms of silence at the start of the track. If you load a track into an editor like audacity you can see the gap. You can edit the track to remove the silence, but it’ll be back when you re-load it. I recommend using the ogg format instead of mp3 as ogg does not add any silence to the start of the track. Switching to the ogg format certainly made an improvement but there was still a small pause between loops.
Incidentally just converting from mp3 to ogg won’t remove the silence automatically. You’ll need to load the mp3 into an editor, remove the gap from the start of the file, then export the track in the ogg format.
With the release of OSX High-Sierra*, one of the new features in Safari is that videos on websites will not auto play anymore and scripts can't start it either, just like on iOS. As a user, I like the feature, but as a developer it puts a problem before me: I have an in-browser HTML5 game that contains video. The videos do not get automatically played anymore unless the user changes their settings. This messes up the game flow.
My question is, can I somehow use the players' interaction with the game as a trigger for the video to start playing automatically, even if said activity is not directly linked to the video element?
I cannot use jQuery or other frameworks, because of a restraint that my employer has put on our development. The one exception is pixi.js which - among all other animations - we are also using to play our videos inside a pixi container.
*The same restriction also applies on Mobile Chrome.
Yes, you can bind on event that are not directly ones triggered on the video element:
btn.onclick = e => vid.play();
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
So you can replace this button with any other splash screen requesting an user click, and you'll be granted access to play the video.
But to keep this ability, you must call at least once the video's play method inside the event handler itself.
Not working:
btn.onclick = e => {
// won't work, we're not in the event handler anymore
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Proper fix:
btn.onclick = e => {
vid.play().then(()=>vid.pause()); // grants full access to the video
setTimeout(()=> vid.play().catch(console.error), 5000);
}
<button id="btn">play</button><br>
<video id="vid" src="https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4"></video>
Ps: here is the list of trusted events as defined by the specs, I'm not sure if Safari limits itself to these, nor if it includes all of these.
Important note regarding Chrome and preparing multiple MediaElements
Chrome has a long-standing bug caused by the maximum simultaneous requests per host which does affect MediaElement playing in the page, limiting their number to 6.
This means that you can not use the method above to prepare more than 6 different MediaElements in your page.
At least two workarounds exist though:
It seems that once a MediaElement has been marked as user-approved, it will keep this state, even though you change its src. So you could prepare a maximum of MediaElements and then change their src when needed.
The Web Audio API, while also concerned by this user-gesture requirement can play any number of audio sources once allowed. So, thanks to the decodeAudioData() method, one could load all their audio resources as AudioBuffers, and even audio resources from videos medias, which images stream could just be displayed in a muted <video> element in parallel of the AudioBuffer.
In my case i was combining transparent video (with audio) with GSAP animation. The solution from Kaiido works perfectly!
First, on user interaction, start and pause the video:
videoPlayer.play().then(() => videoPlayer.pause());
After that you can play it whenever you want. Like this:
const tl = gsap.timeline();
tl.from('.element', {scale: 0, duration: 5);
tl.add(() => videoPlayer.play());
Video will play after the scale animation :).
Tested in Chrome, Safari on iPhone
I have a list of video tags which I need to play one by one with preset currentTime. When I load the page the readyState of videos get stuck at 1 and the video gives a starting glitch. I have used preload attribute still the video takes time to start playing on every switch. Even if some of the videos have currentTime set and readyState = 4 it takes time to play the video. I looked into xhr createObjectURL blob method but that takes too long for all the videos to get downloaded. For the same reason I did not try MediaSource API.
The media source extension (MSE) does not require you to download the whole video before you play it.
It allows you request a video segment by segment and manipulate the segments any way you want before you set them as the source for the video player.
There is a good overview along with some sample javascript which I think helps to understand the approach here: https://www.html5rocks.com/en/tutorials/eme/basics/
and you can see a simple working example here: https://github.com/bitmovin/mse-demo/blob/master/index.html
The general approach is:
create a MediaSource object
Set the source of the video element in your HTML page to the MediaSource object
Add a listener for the MediaSource being opened (when the video is played)
get the first segment and add a listener to request the next segment
as segments are received append them to the MediaSource buffer
when there are no more segments to be requested stop
In your case you can immediately start to request the next video when you get to the end of the first.
One other thing twitch for - mp4 videos often have their metadata at the end which means you need to download the entire video to start. You can move the metadata to the start using special tools or simply make sure you put it there in the first place if you are doing the transcoding yourself. ffmpeg supports moving the data with the command line option '-movflags faststart', for example.
I have a MP4/H264 video clip which is being captured so that it grows every 4 seconds and its metadata is dynamically refreshed. Since this is not fragmented MP4 I cannot use MediaSource API to manipulate chunks.
I'm looking for a way to update/refresh the duration of the video during playback without the need to reload the whole clip.
In short words I'm looking for a way to do the following in more user-friendly way.
setInterval(function() {
video.src = video.src;
}, 4000);
I'd like to avoid having 2 video tags and switching from one to another with the method above. I have also tried with popcorn.js without any luck.
Using Chrome, and... only chrome so not worried about other browsers.
Any advice would be greatly appreciated!
I am not sure that is possible. As per specs:
If a src attribute of a media element is set or changed, the user agent must invoke the media element's media element load algorithm. (Removing the src attribute does not do this, even if there are source elements present.)
So if you touch the video.src the browser should invoke implicitly video.load(). In your case (setInterval) Chrome does this.
I guess you already went the route of saving the currentTime of the video before changing the src and applying it after the src change (wait for the canplay event in this case and call video.play() to resume playing)? I guess you would have some stuttering for 4 seconds refresh in your case.
It seems that you are trying to emulate a live stream as an on demand feed and I do not know a way to do this with progressive download of mp4 (read with un-fragmented MP4).
Related article.
Thanks