How to create a frame-accurate video sequencer with HTML5 <video>? - javascript

I am attempting to build a video sequencer that is capable of playing back a list of video URLs seamlessly in series. There cannot be a gap between the videos; the playback needs to be as close to frame-accurate as possible.
Attempt #1
I used a fairly obvious and straightforward approach, similar to that mentioned in this thread.
HTML 5 video or audio playlist
The issue with this approach was that each time one video ended and the subsequent video was specified as the new source, that new video still needed to load, resulting in a potentially long gap. If it is possible to force all the videos to preload even before they are named in video.src, this approach could still potentially work.
Attempt #2
I rewrote the script so each video in the list would result in a separate video element being created, but not attached to the document (using createElement). Then as each video ended, I removed the previous node and appended the next element.
Code executed on page load:
for (i in timelinePlay)
if (timelinePlay[i] != null)
{
var element = document.createElement('video');
element.id = 'video1-' + (i);
element.src = timelinePlay[i]['URL'];
element.preload = 'load';
video1Array[i] = element;
}
Code for video 'ended' event:
videoElement.addEventListener("ended", function (event) {
if (this.currentTime > 0)
{
if (player.hasChildNodes())
while (player.childNodes.length > 0)
player.removeChild(player.firstChild);
player = document.getElementById('canvas-player');
player.appendChild(video1Array[timelineCurrent]);
nextVideo = document.getElementById('video1-' + timelineCurrent);
nextVideo.play();
timelineCurrent++;
}
}, false);
(Note that these code examples are partial and somewhat simplified. Also, I do realize that my use of Objects as Associative Arrays is not best practice)
The result of this modification was MUCH closer, because the videos were now loaded by the time they were required to play, but there was still a short and inconsistent gap between the videos.
Attempt #3
I replaced the 'ended' event listener with a 'timeupdate' listener, beginning as follows:
nextVideo.addEventListener("timeupdate", function (event)
{
if (this.currentTime > (this.duration - 0.1) && this.currentTime > 1)
{
this.removeEventListener("timeupdate", function () { return; }, false);
('this.currentTime > 1' is simply to ensure that the previous video actually plays)
My hope was that the gap between videos was close enough to being consistent that starting the next video an arbitrary 0.1 seconds before the previous video ended would correct the gap to an acceptable extent. The result was that the timing was indeed better, but it was skipping videos. I attribute the skipped videos to misfiring of the 'timeupdate' event, but I could be mistaken.
Other alternative options I had also considered:
SMIL Script (seems to be obsolete, and would likely have the same syncing issues anyway)
ffmpeg on the backend to concatenate the videos (my host will not allow shell scripts)
FYI I am developing for Safari 5.0.3 at the moment

I had a similar problem that I managed to solve now thanks to your hints. The result is a possibly dynamic list of video elements that may be played back one after another without gaps.
Instead of a native Video Tag I use jwplayer on several video elements.
On startup all elements begin to play one second, are paused and rewound to zero.
Then one by another is played and made visible with display: block.
<ul class="playlist">
<li>
<video class="playlist" id="vid01" poster="img/preview.jpg">
<source src="vid/v01.mp4" type="video/mp4">
</video>
</li>
<li>
<video class="playlist" id="vid02" poster="img/preview.jpg">
<source src="vid/v02.mp4" type="video/mp4">
</video>
</li>
<li>
<video class="playlist" id="vid03" poster="img/preview.jpg">
<source src="vid/v03.mp4" type="video/mp4">
</video>
</li>
<li>
<video class="playlist" id="vid04" poster="img/preview.jpg">
<source src="vid/v04.mp4" type="video/mp4">
</video>
</li>
</ul>
On added to stage, 'preload' the video one or two seconds:
(Excerpt)
var isPrebuffering = false,
isSeeking = false;
$player = jwplayer(videoId).setup({
width: '800px',
height: '450px',
autoplay: false,
modes: [
{ type: 'html5' }
],
events: {
onReady: function(e) {
prebuffer();
},
// There is a quirk in jwplayer that makes it impossible to pause at the
// new position directly after seeking. So we have to work around that.
onTime: function(e) {
if((e.position > 1.0 && isPrebuffering) || isSeeking) {
finishPrebuffering();
}
}
}
});
function prebuffer() {
isPrebuffering = true;
$player.setMute(true);
$player.play();
};
function finishPrebuffering() {
if(isPrebuffering) {
isPrebuffering = false;
isSeeking = true;
$player.seek(0);
}
if($player.getPosition() === 0) {
isSeeking = false;
$player.pause();
$player.setMute(false);
}
};

The mediaElement interface of HTML5 can be quite confusing sometimes between preload, load and buffered.
But have you tried to put an event on your video to detect for example 3 seconds before the end of your video ?
Then in ajax try to load the next video in a new div.
On the ended event of your video you could switch the div, so your next video will be enough loaded to be played.
And so on for the next videos.
http://www.w3.org/TR/html5/video.html#media-elements

You might be able to do so with http://popcornjs.org/. They provide js hooks, on a frame by frame level into the video using html5 video tag. There are a lot of events etc.

Related

How can I get current time of autoplayed html5 mp4 video in javascript and then stop it when time reaches for lets say 5 seconds?

So, I need to autoplay couple of videos with same class on page load. That is fine.
Then, when videos starts playing, when they reach for 1 second, I need them to stop automatically. The other part is what I need.
<video playsinline muted loop class="video">
<source src="video-source">
</video>
let videos = document.querySelectorAll('.video');
window.addEventListener('DOMContentLoaded', () => {
videos.forEach(el => {
el.play();
});
});
I have tried doing something like this, get currentTime and then make an IF statement but no luck. It always console loggs 0.
function myFunction() {
videos.forEach(el => {
console.log(el.currentTime);
if(el.currentTime = 5) {
el.pause();
}
});
}
Thanks!
The issue you will have with your current attempt is that it only runs once, you need to have a way of checking your logic at intervals so that you can continuously check the time lapsed.
You could potentially look at using the timeupdate video event, this runs repeatedly whilst a video is playing.

How to handle "Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first." on Desktop with Chrome 66?

I'm getting the error message..
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
..when trying to play video on desktop using Chrome version 66.
I did find an ad that began playback automatically on a website however using the following HTML:
<video
title="Advertisement"
webkit-playsinline="true"
playsinline="true"
style="background-color: rgb(0, 0, 0); position: absolute; width: 640px; height: 360px;"
src="http://ds.serving-sys.com/BurstingRes/Site-2500/Type-16/1ff26f6a-aa27-4b30-a264-df2173c79623.mp4"
autoplay=""></video>
So is by-passing Chrome v66's autoplay blocker really as easy as just adding the webkit-playsinline="true", playsinline="true", and autoplay="" attributes to the <video> element? Are there any negative consequences to this?
To make the autoplay on html 5 elements work after the chrome 66 update you just need to add the muted property to the video element.
So your current video HTML
<video
title="Advertisement"
webkit-playsinline="true"
playsinline="true"
style="background-color: rgb(0, 0, 0); position: absolute; width: 640px; height: 360px;"
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
autoplay=""></video>
Just needs muted="muted"
<video
title="Advertisement"
style="background-color: rgb(0, 0, 0); position: absolute; width: 640px; height: 360px;"
src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
autoplay="true"
muted="muted"></video>
I believe the chrome 66 update is trying to stop tabs creating random noise on the users tabs. That's why the muted property make the autoplay work again.
For me (in Angular project) this code helped:
In HTML you should add autoplay muted
In JS/TS
playVideo() {
const media = this.videoplayer.nativeElement;
media.muted = true; // without this line it's not working although I have "muted" in HTML
media.play();
}
Try to use mousemove event listener
var audio = document.createElement("AUDIO")
document.body.appendChild(audio);
audio.src = "./audio/rain.m4a"
document.body.addEventListener("mousemove", function () {
audio.play()
})
The best solution i found out is to mute the video
HTML
<video loop muted autoplay id="videomain">
<source src="videoname.mp4" type="video/mp4">
</video>
Answering the question at hand...
No it's not enough to have these attributes, to be able to autoplay a media with audio you need to have an user-gesture registered on your document.
But, this limitation is very weak: if you did receive this user-gesture on the parent document, and your video got loaded from an iframe, then you could play it...
So take for instance this fiddle, which is only
<video src="myvidwithsound.webm" autoplay=""></video>
At first load, and if you don't click anywhere, it will not run, because we don't have any event registered yet.
But once you click the "Run" button, then the parent document (jsfiddle.net) did receive an user-gesture, and now the video plays, even though it is technically loaded in a different document.
But the following snippet, since it requires you to actually click the Run code snippet button, will autoplay.
<video src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm" autoplay=""></video>
This means that your ad was probably able to play because you did provide an user-gesture to the main page.
Now, note that Safari and Mobile Chrome have stricter rules than that, and will require you to actually trigger at least once the play() method programmatically on the <video> or <audio> element from the user-event handler itself.
btn.onclick = e => {
// mark our MediaElement as user-approved
vid.play().then(()=>vid.pause());
// now we can do whatever we want at any time with this MediaElement
setTimeout(()=> vid.play(), 3000);
};
<button id="btn">play in 3s</button>
<video
src="https://upload.wikimedia.org/wikipedia/commons/transcoded/2/22/Volcano_Lava_Sample.webm/Volcano_Lava_Sample.webm.360p.webm" id="vid"></video>
And if you don't need the audio, then simply don't attach it to your media, a video with only a video track is also allowed to autoplay, and will reduce your user's bandwidth usage.
Extend the DOM Element, Handle the Error, and Degrade Gracefully
Below I use the prototype function to wrap the native DOM play function, grab its promise, and then degrade to a play button if the browser throws an exception. This extension addresses the shortcoming of the browser and is plug-n-play in any page with knowledge of the target element(s).
// JavaScript
// Wrap the native DOM audio element play function and handle any autoplay errors
Audio.prototype.play = (function(play) {
return function () {
var audio = this,
args = arguments,
promise = play.apply(audio, args);
if (promise !== undefined) {
promise.catch(_ => {
// Autoplay was prevented. This is optional, but add a button to start playing.
var el = document.createElement("button");
el.innerHTML = "Play";
el.addEventListener("click", function(){play.apply(audio, args);});
this.parentNode.insertBefore(el, this.nextSibling)
});
}
};
})(Audio.prototype.play);
// Try automatically playing our audio via script. This would normally trigger and error.
document.getElementById('MyAudioElement').play()
<!-- HTML -->
<audio id="MyAudioElement" autoplay>
<source src="https://www.w3schools.com/html/horse.ogg" type="audio/ogg">
<source src="https://www.w3schools.com/html/horse.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
I got this error
Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first.
And here's what I did in my Angular Project
Key Point: Don't ever assume a video will play, and don't show a pause button when the video is not actually playing.
You should always look at the Promise returned by the play function to see if it was rejected:
ngOnInit(): void{
this.ensureVideoPlays();
}
private ensureVideoPlays(): void{
const video = document.querySelector("video");
if(!video) return;
const promise = video.play();
if(promise !== undefined){
promise.then(() => {
// Autoplay started
}).catch(error => {
// Autoplay was prevented.
video.muted = true;
video.play();
});
}
}
Source: Autoplay policy
In my case, I had to do this
// Initialization in the dom
// Consider the muted attribute
<audio id="notification" src="path/to/sound.mp3" muted></audio>
// in the js code unmute the audio once the event happened
document.getElementById('notification').muted = false;
document.getElementById('notification').play();
According to the new browser policy, the user must interact with DOM first before playing the Audio element.
If you want to play the media on page load then you can simply add autoplay property to audio element in HTML like this
<video id="video" src="./music.mp4" autoplay>
or if you don't want to do autoplay then you can handle this using Javascript. Since the autoplay property is set to true, media will be played, we can simply mute the media.
document.getElementById('video').autoplay = true;
document.getElementById('video').muted = true;
Imp: Now Whenever you play the media don't forget to turn the muted property to false. Like this
document.getElementById('video').muted = false;
document.getElementById('video').play();
Or you can also show a simple popup where the user will click the allow button in the modal. So he interacts with DOM first, then you don't need anything to do
I had a similar problem, I need to play the video without muting it. The way i did this, wait for a second then triggered the event by button. Here is my code
if (playVideo == '1') {
setTimeout(function() {
$("#watch_video_btn").trigger('click');
}, 1000);
}
Chrome needs a user interaction for the video to be autoplayed or played via js (video.play()).
But the interaction can be of any kind, in any moment.
If you just click random on the page, the video will autoplay.
I resolved then, adding a button (only on chrome browsers) that says "enable video autoplay". The button does nothing, but just clicking it, is the required user interaction for any further video.
I changed my UI to have the user press a button to load the website (and when the website loads after they click the button, the audio plays)
Since they interact with the DOM, then the audio plays!!!
In my case it's just a click sound which is automatically invoked at the start (which I don't mind if it's silenced). So I use:
const clickSound = new Audio('click.wav');
clickSound.play().catch(function (error) {
console.log("Chrome cannot play sound without user interaction first")});
to get rid of the error.
I had some issues playing on Android Phone.
After few tries I found out that when Data Saver is on there is no auto play:
There is no autoplay if Data Saver mode is enabled. If Data Saver mode is enabled, autoplay is disabled in Media settings.
Source
I encountered a similar error with while attempting to play an audio file. At first, it was working, then it stopped working when I started using ChangeDetector's markForCheck method in the same function to trigger a re-render when a promise resolves (I had an issue with view rendering).
When I changed the markForCheck to detectChanges it started working again. I really can't explain what happened, I just thought of dropping this here, perhaps it would help someone.
You should have added muted attribute inside your videoElement for your code work as expected. Look bellow ..
<video id="IPcamerastream" muted="muted" autoplay src="videoplayback%20(1).mp4" width="960" height="540"></video>
Don' t forget to add a valid video link as source
Open chrome://settings/content/sound
Setting No user gesture is required
Relaunch Chrome
Audio Autoplay property does not work in MS Edge
Type Chrome://flags in the address-bar
Search: Autoplay
Autoplay Policy
Policy used when deciding if audio or video is allowed
to autoplay.
– Mac, Windows, Linux, Chrome OS, Android
Set this to "No user gesture is required"
Relaunch Chrome and you don't have to change any code

Load new video automatically when VideoJS MP4 ends

In a project I am currently working on, I have a VideoJS player which I want to change the source of when the video ends.
Using the VideoJS API, I am able to get a Javascript script to run when the player has reached an 'ended' state, but I can't find any code in their documentation or on StackOverflow explaining how to do this.
HTML
<video
id="my_video_1"
style="display:block;"
class="video-js vjs-default-skin vjs-nofull vjs-big-play-centered"
controls autoplay preload="none"
width="800px"
poster='res/img/poster.jpg'
data-setup='{ "fluid": true, "sources": [{ "type": "video/mp4", "src": "res/vid/vid1.mp4"}] }'
>
</video>
Is there any way of dynamically changing the src to a new file when the video ends?
The VideoJS has an API of 'player.on' which is then followed by whichever listener is needed - in this case, ('ended').
Where I was falling down was that I was trying to use document.GetElementByID and update the src that way.
By changing the VideoJS code in the HTML to the style above, I was able to run the below listening event to update the src on ended and load a new video.
The next issue I had was the video was looping. When the video ended, the same function was called infinitely.
To counter this, I added a Boolean value of 'executed' which changes to True on the first play.
<script>
var video = videojs('my_video_1').ready(function(){
var player = this;
var executed = false;
player.on('ended', function() {
if (!executed) {
player.src({"type":"video/mp4", "src":"res/vid/vid2.mp4"});
player.play();
executed = true
}
});
});
</script>
The first video now plays, followed by the second one when the first has finished without any user interaction.
I hope this helps anyone else who may experience a similar issue.

JQuery play/pause on HTML5 video is slow

I have an HTML5 video that I want to play/pause when you click anywhere on the video. The code I wrote below works, but if I click the video a few times it starts to get slow and will even freeze sometimes. I saved the selectors in variables hoping that would take care of the issue, but it hasn't made a noticeable difference. Is there a bug in my code that I'm not seeing and the console isn't detecting? Or is there just a better way to write this so it isn't so slow? By the way, the intro-vid ID is on the <video> element in the HTML.
var $video = $('video')[0];
var $introVid = $('#intro-vid');
// If the video is playing, pause it when clicked
$introVid.on('play', function() {
$introVid.click(function() {
$video.pause();
});
});
// If the video is paused, play it when clicked
$introVid.on('pause', function() {
$introVid.click(function() {
$video.play();
});
});
EDIT: Here is the HTML
<video id="intro-vid" controls>
<source src="placeholder.mp4" type="video/mp4">
<source src="placeholder.webm" type="video/webm">
Your browser does not support the video tag.
</video>
You should not bind event handlers inside of other handlers, this way them quickly multiply causing troubles. Try this instead:
var $video = $('video')[0];
var $introVid = $('#intro-vid');
$introVid.click(function () {
$video.paused ? $video.play() : $video.pause();
});

Pausing video doesn't stop audio in html5 video tag

I'm pausing a video using its pause() method.. the problem is that the audio continues playing... I also tried pausing it from the Javascript Console in Firefox... nothing happens. The video is in .ogg format and is not even playing in Chrome (because I think it's not supported).
I hosted the video on Amazon S3 and it is streaming perfectly. I'm creating the element dynamically, loading its info from a JSON request.
Here is some code:
function showVideo() {
var video = videodata;
var videobox = $('#videobox').first();
var videoplayer = document.getElementById('videoplayer');
if (video.Enabled) {
if ((videoplayer != null && videoplayer.currentSrc != video.Location) || videoplayer == null) {
console.log('Creating video elem');
videobox.empty();
videobox.append('<video id="videoplayer" preload="auto" src="' +
video.Location + '" width="100%" height="100%" autoplay="autoplay" loop="loop" />');
videobox.show();
}
} else {
if (videoplayer != null) {
videoplayer.pause();
console.log('Pausing video...');
}
console.log('Deleting video elem');
videobox.hide();
videobox.empty();
}
}
I already posted a similar question before... but now I'm using other browsers, so I thought I have to create a new question.
Here is the working code (thanks to the user heff!)
function showVideo() {
var video = videodata;
var videobox = $('#videobox').first();
var videoplayer = document.getElementById('videoplayer');
if (video.Enabled) {
if ((videoplayer.src != video.Location) || videoplayer.src == '') {
console.log('Playing video: ' + video.Location);
videoplayer.src = video.Location;
videoplayer.load();
videoplayer.play();
videobox.show();
}
} else {
if (videoplayer.src != '') {
console.log('Pausing video...');
videoplayer.pause();
videoplayer.src = '';
videobox.hide();
}
}
}
I had a similar issue which was resolved with really wonderful coders. Check out this post. It might be of great use. HTML5 Video Controls do not work
If you are using html attribute:
<video id="yourvideoID" poster="Pic.jpg" loop autoplay controls width="100%">
<source src="//example.website/InnovoThermometer.mp4" type="video/mp4">
</video>
Remove autoplay.
Then use jquery to use autoplay instead:
$(document).ready(function() { $("#bigvid3").get(0).play(); });
As for the source there are multiple locations:
html5 video playing twice (audio doubled) with JQuery .append()
and
Auto-play an HTML5 video on Dom Load using jQuery
My guess would be that showVideo is being called twice some how and creating two copies, one of which keeps playing even after you call pause on the other.
In your code, the videoplayer var won't refer to the video tag you create later with append, it will point to whatever had that id before, which I'm assuming gets removed when you empty the box, but might stick around in memory (and continue to play sound).
Just my best guess, but either way it'd be better to use the video element's API to set the source and other parameters rather than emptying the box and rebuilding the tag.
videoplayer.src = video.Location;
videoplayer.autoplay = true;
// etc.
Also, 100% isn't a valid value for the width/height attributes. You'll need to use CSS to make the video stretch to fill an area.

Categories