Using 'this.currentTime' to get the time of a video and reset it to the starting point on 'hover out' - javascript

I have a video library where I want to dynamically use the Media Fragment time in URL as the poster.
When hovering out, I am trying to reset the video to the initial start - to make sure the poster is at 2 seconds (in this specific example) instead of 0.
this.load works but creates a bad user experience as the whole video loads in again.
My idea is to define the current time as a variable (before the video starts playing) and use it when pausing the video.
However I just get "Uncaught ReferenceError: posterTime is not defined".
<video id="video">
<source src="videourl.mp4#t=2" type="video/mp4">
</video>
const videos = document.querySelectorAll("video")
videos.forEach(video => {
video.addEventListener("mouseover", function () {
var posterTime = this.currentTime;
this.currentTime = 0;
this.play()
})
video.addEventListener("mouseout", function () {
this.currentTime = posterTime;
this.pause();
})
})
Note that I use Webflow and is not very strong with jQuery/Javascript.

My idea is to define the current time as a variable (before the video
starts playing) and use it when pausing the video. However I just get
"Uncaught ReferenceError: posterTime is not defined".
Your idea and code is fine but you made a basic mistake.
Remember: A variable defined inside a function will exist only for that function where it was created.
Use let for internal variables (where possible) and use var for global variables.
solution: Define the variable as global (outside of any functions)...
const videos = document.querySelectorAll("video");
var posterTime = -1; //# global var, with starting value...
videos.forEach(video => {
video.addEventListener("mouseover", function ()
{
posterTime = this.currentTime; //# set time
this.currentTime = 0;
this.play()
})
video.addEventListener("mouseout", function ()
{
this.currentTime = posterTime; //# get time
this.pause();
})
})

As I hover out I need it to show that initial frame again (not the first frame, but the one set in the URL)
Given this requirement you can retrieve the fragment from the URL in the src attribute of the source element and apply it to the currentTime of the video when the mouseleave event occurs:
const videos = document.querySelectorAll("video")
videos.forEach(video => {
video.addEventListener("mouseenter", function() {
this.currentTime = 0;
this.play()
})
video.addEventListener("mouseleave", function() {
let src = this.querySelector('source').src;
let time = (src.split('#')[1] || 't=0').split('=')[1];
this.currentTime = time;
this.pause();
})
})
<video id="video">
<source src="http://grochtdreis.de/fuer-jsfiddle/video/sintel_trailer-480.mp4#t=5" type="video/mp4">
</video>

Related

Any way to get a currentTime value prior to the seek on HTMLMediaElement?

Let's say our app is using the default video player on Safari.
When a user is playing a video and then attempts to move to a different position of the video using the seek bar, it seems like pause event is fired first, and then we'll get seeking and seeked events fired.
I am wondering if we can get the currentTime value prior to the seek. For instance, assuming that a user jumps from t = 7 to t = 42 using the seek bar, I want to get 7 as the currentTime value somehow.
I expected that we could get this value by accessing currentTime property inside the pause event handler that is invoked right after the seek like the following:
const video = document.querySelector('#myvideo');
video.addEventListener('pause', () => {
// I expected that the `video.currentTime` here has the "previous" position,
// but it already points to the new position
console.log(video.currentTime);
});
but unfortunately the currentValue was already updated to the new value at that point.
Is there any good way to achieve it?
(EDIT)
Caching currentTime manually doesn't help, because apparently a timeupdate event fires before a pause event. More specifically, taking the following code as an example, when a user attempts to jump to another position, cache and currentTime printed within the pause handler seem always identical.
<!DOCTYPE html>
<html>
<body>
<video
id="myvideo"
width="640"
height="360"
controls
src="video.mp4"
></video>
</body>
<script>
const video = document.querySelector("#myvideo");
let cache = 0;
video.addEventListener("timeupdate", () => {
cache = video.currentTime;
});
video.addEventListener("pause", () => {
console.log({ cache, currentTime: video.currentTime });
});
</script>
</html>
I think #Kaiido means this when saying "Cache two values".
Code is untested (but looks better than being kept in comments section)
<script>
const video = document.querySelector("#myvideo");
let cache = 0;
let cache_prev = 0;
video.addEventListener("timeupdate", () => {
cache_prev = cache; //# save last known value
cache = video.currentTime; //# before updating to new currentTime
});
video.addEventListener("pause", () => {
console.log("cache_prev : " + cache_prev );
console.log("cache : " + cache );
console.log("currentTime : " + video.currentTime );
});
</script>

Multiple HTML5 videos audio captions not working correctly

I am adding multiple HTML5 videos onto a webpage.
The code I am replicating is from this recommended accessible approach. http://jspro.brothercake.com/audio-descriptions/ The video plays fine, and audio captions work, but when I add a new video to the same page the second video does not play the audio captions at all. Does anyone have suggestions on how I can fix this issue?
<video id="video" preload="auto" controls="controls"
width="640" height="360" poster="./media/HorribleHistories.jpg">
<source src="./media/HorribleHistories.mp4" type="video/mp4" />
<source src="./media/HorribleHistories.webm" type="video/webm" />
</video>
<audio id="audio" preload="auto">
<source src="./media/HorribleHistories.mp3" type="audio/mp3" />
<source src="./media/HorribleHistories.ogg" type="audio/ogg" />
</audio>
<script type="text/javascript">
(function()
{
//get references to the video and audio elements
var video = document.getElementById('video');
var audio = document.getElementById('audio');
//if media controllers are supported,
//create a controller instance for the video and audio
if(typeof(window.MediaController) === 'function')
{
var controller = new MediaController();
audio.controller = controller;
video.controller = controller;
}
//else create a null controller reference for comparison
else
{
controller = null;
}
//reduce the video volume slightly to emphasise the audio
audio.volume = 1;
video.volume = 0.8;
//when the video plays
video.addEventListener('play', function()
{
//if we have audio but no controller
//and the audio is paused, play that too
if(!controller && audio.paused)
{
audio.play();
}
}, false);
//when the video pauses
video.addEventListener('pause', function()
{
//if we have audio but no controller
//and the audio isn't paused, pause that too
if(!controller && !audio.paused)
{
audio.pause();
}
}, false);
//when the video ends
video.addEventListener('ended', function()
{
//if we have a controller, pause that
if(controller)
{
controller.pause();
}
//otherwise pause the video and audio separately
else
{
video.pause();
audio.pause();
}
}, false);
//when the video time is updated
video.addEventListener('timeupdate', function()
{
//if we have audio but no controller,
//and the audio has sufficiently loaded
if(!controller && audio.readyState >= 4)
{
//if the audio and video times are different,
//update the audio time to keep it in sync
if(Math.ceil(audio.currentTime) != Math.ceil(video.currentTime))
{
audio.currentTime = video.currentTime;
}
}
}, false);
})();
</script>
So your problem is to do with how you are grabbing the elements in the first place.
var video = document.getElementById('video');
var audio = document.getElementById('audio');
What you are doing is grabbing a single item on the page with the ID of "video" (same for "audio").
IDs have to be unique, so what you want to do is use classes instead.
<video class="video" preload="auto" controls="controls"
width="640" height="360" poster="./media/HorribleHistories.jpg">
See I changed the ID to a class.
Now any element with the class "video" can be used in our code.
However we do need to modify our code a bit as now we have multiple items to bind to.
please note the below is to give you an idea of how you loop items etc. You would need to rewrite your code to move each of the steps into functions etc. as your original code is not designed to work with multiple items
(function()
{
//get references to every single video and audio element
var videos = document.querySelectorAll('.video');
var audios = document.querySelectorAll('.audio');
// loop through all videos adding logic etc.
for(x = 0; x < videos.length; x++){
// grab a single video from our list to make our code neater
var video = videos[x];
if(typeof(window.MediaController) === 'function')
{
var controller = new MediaController();
video.controller = controller;
} else {
controller = null;
}
video.volume = 0.8;
//...etc.
}
})();
Quick Tip:
I would wrap your <video> and <audio> elements that are related in a <div> with a class (e.g. class="video-audio-wrapper").
This way you can change your CSS selector to something like:
var videoContainers = document.querySelectorAll('.video-audio-wrapper');
Then loop through them instead and check if they have a video and / or audio element
for(x = 0; x < videoContainers.length; x++){
var thisVideoContainer = videoContainers[x];
//query this container only - we can use `querySelector` as there should only be one video per container and that returns a single item / the first item it finds.
var video = thisVideoContainer.querySelector('video');
var audio = thisVideoContainer.querySelector('audio');
//now we can check if an element exists
if(video.length == 1){
//apply video logic
}
if(audio.length == 1){
//apply audio logic
}
// alternatively we can check both exist if we have to have both
if(video.length != 1 || audio.length != 1){
// we either have one or both missing.
// apply any logic for when a video / audio element is missing
//using "return" we can exit the function early, meaning all code after this point is not run.
return false;
}
///The beauty of this approach is you could then just use your original code!
}
Doing it this way you could recycle most of your code.
Thank you for your suggestions in changing the ID's into classes and adding the video wrapper <div> to the video container. That all makes sense in grouping each video on 1 page. I updated the the following code, but the audio captions won't play at all. The video plays and pauses fine, and the volume works. I am also not getting any syntax errors in the browser console. Here's what I got for my HTML and JS. I appreciate your help/feedback.
<div class="video-container-wrapper">
<div class="video-container">
<video class="video" preload="auto" controls="controls" width="640" height="360" poster="img/red-zone-thumb.png">
<source src="https://player.vimeo.com/external/395077086.hd.mp4?s=1514637c1ac308a950fafc00ad46c0a113c6e8be&profile_id=175" type="video/mp4">
<track kind="captions" label="English captions" src="captions/redzone-script.vtt" srclang="en" default="">
</video>
<audio class="audio" preload="auto">
<source src="captions/redzone-message.mp3" type="audio/mp3">
</audio>
</div>
</div>
Javascript:
var videoContainers = document.querySelectorAll('.video-container-wrapper');
for (x = 0; x < videoContainers.length; x++) {
var thisVideoContainer = videoContainers[x];
//query this container only - we can use `querySelector` as there should only be one video per container and that returns a single item / the first item it finds.
var video = thisVideoContainer.querySelector('video');
var audio = thisVideoContainer.querySelector('audio');
//now we can check if an element exists
if (video.length == 1) {
//apply video logic
//reduce the video volume slightly to emphasise the audio
video.volume = 0.8;
//when the video ends
video.addEventListener('ended', function () {
video.pause();
}, false);
}
if (audio.length == 1) {
//apply audio logic
audio.volume = 1;
//when the video plays
video.addEventListener('play', function () {
if (audio.paused) {
audio.play();
}
}, false);
// when the video ends
video.addEventListener('ended', function () {
audio.pause();
}, false);
//when the video time is updated
video.addEventListener('timeupdate', function () {
if (audio.readyState >= 4) {
//if the audio and video times are different,
//update the audio time to keep it in sync
if (Math.ceil(audio.currentTime) != Math.ceil(video.currentTime)) {
audio.currentTime = video.currentTime;
}
}
}, false);
}
}

Can I create a breakable loop with currentTime of JavaScript?

I'm trying to create a loop of a video and when I click some button break that loop to finish the video
function stopIt() {
document.getElementById('stopIt').classList.add('stop');
}
function loopy() {
var vid = document.getElementById('front-video');
if (vid.currentTime > 1) {
vid.currentTime = 0;
if (document.getElementById('stopIt').classList.contains('stop')) {
break;
}
}
}
<video class="d-none d-lg-block" id="front-video" autoplay onTimeUpdate="loopy()">
<source src="video/front-video.mp4" type="video/mp4">
</video>
<div class="col-12 front-box py-3 pl-4" id="stopIt" onClick="stopIt()"></div>
I have a box with the id stopIt, but actually the video doesn't return to the start, It does it when I erase the second if.
Any ideas, thanks!
I'm not sure if I understand you correctly, but I think you want to do this:
function loopy() {
if (!document.getElementById('stopIt').classList.contains('stop')) {
var vid = document.getElementById('front-video');
if (vid.currentTime > 1) vid.currentTime = 0;
}
}
If the requirement is to loop from a specific begin time in media playback to a specific end time in media playback you can set the initial src of the HTMLMediaElement to the URL followed by a media fragment identifier.
The media element will pause at the end time of the media fragment identifier. You can use pause event, which is dispatched when a media element reaches the end time of a media fragment identifier, where .load() then .play() can be called to loop the media playback.
At click on button element, remove pause event handler from button element, store the .currentTime in a variable, remove the media fragment identifier from the src of the media element, call .load() on media element, set .currentTime to the stored .currentTime and then call .play().
const video = document.querySelector('video');
const button = document.querySelector('button');
let [from, to] = [0, 1];
let loop = true;
let currentTime = 0;
const src = "http://mirrors.creativecommons.org/movingimages/webm/ScienceCommonsJesseDylan_240p.webm";
const handlePause = e => {
if (loop) {
video.load();
video.play();
}
}
const stopLoop = e => {
if (loop) {
loop = !loop;
video.removeEventListener('pause', handlePause);
currentTime = video.currentTime;
video.src = video.src.replace(/#.+$/, '');
video.load();
video.currentTime = currentTime;
video.play();
}
}
button.addEventListener('click', stopLoop)
video.addEventListener('pause', handlePause);
video.src = `${src}#t=${from},${to}`
<button>stop loop</button>
<video src="" muted="false" controls autoplay>

Change HTML 5 autoplayed video source dynamically

On my website https://cravydev.netlify.com I'm trying to change the video dynamically when the different headlines are in viewport.
I'm using a div with HTML 5 autoplay and loop properties, but not able yet to change the video source and load again when the specific headline is on screen. Here are the details.
HTML
<div class="iphone-sticky"></div>
<div class="iphone-video">
<video autoplay loop muted poster="assets/img/poster.png" id="iphonevideo">
<source id="video1" src="assets/img/iphone-video.mp4" type="video/mp4">
<source id="video2" src="assets/img/iphone-video-1.mp4" type="video/mp4">
</video>
JS
//Detect is element is in viewport
$(window).on('resize scroll', function() {
var player = document.getElementById('iphonevideo2');
if ($('#headline2').isInViewport()) {
var source = document.getElementById('video1');
$(source).attr("src", "assets/img/iphone-video-1.mp4");
player.pause();
player.load();
} else if ($('#headline3').isInViewport()) {
var source = document.getElementById('video1');
$(source).attr("src", "assets/img/iphone-video.mp4");
player.pause();
player.load();
}
});
EDIT
I have been able to solve it thanks to Kley comment. Now the problem is that the js logic does not check constantly the viewport visibility of headlines, only once. Also, the video is played while scrolling and the poster image appears, making the whole experience a little bit weird.
Any suggestion to solve that?
Thanks!
Check the page, it's not the viewport ()
I could tell you that you are changing the src every time you scroll while it is visible, this is causing the video to restart. You must make a validation, if it has already been changed the src should not be changed again.
Note that # headline2 is not visible when # headline3 is a bit up.
you have to scroll down until the end of the page to enter this condition.
You could use another smaller element at the beginning of each #headline to do this validation.
you could use the first p of the #headline
For example:
$(window).on('resize scroll', function() {
var player = document.getElementById('iphonevideo2');
if ($('#headline2 p').eq(0).isInViewport()) {
var source = document.getElementById('video1');
var url = "assets/img/iphone-video-1.mp4";
var src = $(source).attr("src");
// validate if you already have the src
if(src==url) return; // if exists src leaves function
$(source).attr("src", url);
player.pause();
player.load();
} else if ($('#headline3 p').eq(0).isInViewport()) {
var source = document.getElementById('video1');
var src = $(source).attr("src");
var url = "assets/img/iphone-video.mp4";
// validate if you already have the src
if(src==url) return; // if exists src leaves function
$(source).attr("src", url);
player.pause();
player.load();
}
})

Display video duration from videoJS

I am working on an HTML5 video player with jQuery. For now I have my video player working very well but I want to get the variable for the video duration and show/display it in pure HTML page.
So from here you can take more info about this Jquery video player:
http://www.videojs.com/docs/api/
I think the variable for video duration is: myPlayer.duration();
How I can display this value in HTML?
Here is my HTML code to display the player:
<video id="vemvo-player" class="video-js vjs-default-skin" controls autoplay="true" width="950" height="534"
data-setup="{}">
<source src="[var.base_url]/uploads/[var.video_play]" type='video/flv' />
</video>
This is what I have tried to display this variable but it says that it is = "0" when on the video player it says that it's 4min:
<video id="vemvo-player" class="video-js vjs-default-skin" controls autoplay="true" width="950" height="534"
data-setup="{}">
<source src="[var.base_url]/uploads/[var.video_play]" type='video/flv' />
</video>
<div id="duration"></div>
<script type="text/javascript">
_V_("vemvo-player").ready(function(){
var myPlayer = this;
var howLongIsThis = myPlayer.duration();
$('#duration').html('Duration: ' + howLongIsThis);
});
</script>
Where is my mistake?
You can try this. It works for me.
var myPlayer = videojs('vemvo-player');
if (myPlayer.readyState() < 1) {
// wait for loadedmetdata event
myPlayer.one("loadedmetadata", onLoadedMetadata);
}
else {
// metadata already loaded
onLoadedMetadata();
}
function onLoadedMetadata() {
alert(myPlayer.duration());
$('#duration').html("Duration: " + myPlayer.duration());
}
var player = videojs('my-video', {
fluid:false, // videojs settings
controls:true,
height: '300'
});
$(document).ready(function(){
console.log(player.duration());
// will return video duration. Can't be used while page is loading.
console.log(player.currentTime());
// will return current time if video paused at some time. Intially it would be 0.
});
Those who might come here and are using videojs with Vue 3, also might work with similar frameworks like React.
Referring to this code and component usage - https://docs.videojs.com/tutorial-vue.html you can do the following changes to get the duration, height, and width.
// New Code here
this.player = videojs(
this.$refs.videoPlayer,
this.options,
function onPlayerReady() {
console.log("onPlayerReady", this);
}
);
this.player.one("loadedmetadata", () => {
var duration = this.player.duration();
console.log(`Duration of Video ${duration}`);
console.log(`Original Width of Video ${this.player.videoWidth()}`);
});
I was having troubles to get the real duration of videos (using videojs v7.18.*) resulting in bugged custom progressbar! The problem was that player.duration() always returns rounded number while player.liveTracker.seekableEnd() returns the real value but only after start event.
For example, if real duration is 13.456s, these values are returned for videojs player object p:
| On ready | On load | On start
----------------------------|-------------|---------|----------
p.duration() | 0 | 14 | 14
p.liveTracker.seekableEnd() | 14 | 14 | 13.456
I've ended up with this solution in React:
const onDurationChange = () => {
const dur = player?.liveTracker.seekableEnd() || player?.duration() || 0;
setDurationInSec(dur);
}
return <video ref={videoRef} onDurationChange={onDurationChange}>
As you're using jQuery, that can be done much easier :) You can use $.html to edit the html contents of a dom element. Your code will look something like this:
<div id="duration"></div>
<script type="text/javascript">
_V_("vemvo-player").ready(function(){
var myPlayer = this;
var howLongIsThis = myPlayer.duration();
$('#duration').html('Duration: ' + howLongIsThis);
});
</script>

Categories

Resources