I'm working on this mobile site, where I need to play a YouTube video in Fullscreen. So far, so good. Just a YouTube embed, and voilá.
But! (Yeah, there's always a «but»)
I need to fire an event (namely, a JS function) when the video ends. I figured Safari translated the YT embed to a <video> tag, so I tried using HTML5s video events, but didn't work:
$('video').bind('ended', function () {
alert('Video has ended');
});
This fine piece of code works when using a <video> tag directly, but not when using a YT embed.
So, any ideas on how can I fire this function when the YT video has finished playing?
Thanks!
When you create your YT instance, you can provide a hash that maps events to handlers. Provide a handler for the onStateChange event, and test for the eventType that corresponds to video end ( 0 ).
new YT.Player('player', {
events: {
'onStateChange' : function ( eventType ) { if ( eventType.data == 0 ) { ... } }
}
} );
See the section of YouTube's JavaScript API for events
Related
Context:
So I built a website for a company, and it uses Vimeo to host all it's videos. We use the "direct download" URL's Vimeo Pro provides to play a MP4 video using the native <video> element.
You can see the site here.
For example, a lot of images on this site show a video on hover, on almost all pages. Those videos are what I am describing. It is not the video's shown with player controls, those are proper Vimeo iFrame embeds.
The problem:
After browsing around the site for a little (navigating to about 4-5 different pages should trigger it), those MP4 videos stop loading, but only in Chrome. I've tested it across a lot of computers in my company, both in office and remote. I am using Chrome for Mac, Version 107.0.5304.121.
What does "stop loading" mean? I mean, that if you copy the video src URL from the site source code and open it in a new browser, it never loads... But if you use that same URL in an Incognito tab, it will load and play. You can also see it happening in the Network tab of DevTools.
From best I can tell, the Vimeo URL actually get redirected to a akamaized.net URL, and that redirect stops working, perhaps by some sort of rate limit or cookie tracking?
Here is a video recording showing the issue:
https://www.dropbox.com/s/fnp0oaoaeb9s54i/New%20Recording%20-%2011_29_2022%2C%2010_32_58%20AM.webm?dl=0
The code that is used to display those videos is like this:
<video
src="https://player.vimeo.com/progressive_redirect/playback/759618180/rendition/1080p/file.mp4?loc=external&signature=73c3773c3830e6ef73af25b0c88e33c411a79a365497ef56519b5f18a963b523"
loop="loop"
autoplay="autoplay"
playsinline="true"
disablepictureinpicture="true"
preload="none"
muted=""
>
</video>
And then using an IntersectionObserver when the video is in-view I load() the video, and on hover I play() the video.
Vimeo support says they are "unable to replicate the issue" which given the multiple people in my company (and the client) that can see this, I think Vimeo support is wrong.
Attempted solutions:
I tried setting crossorigin="anonymous" on each video, and that had no effect.
I implemented the Intersection Observer and preload="none" code to be more efficient with what videos get loaded. This helped with bandwidth usage, but didn't solve the Vimeo video's not loading/redirecting.
Intersection Observer:
A comment asked for the Intersection Observer code, so here it is. This is a big Vue component using a custom IntersectionObserver directive, so I simplified it some.
<figure
v-intersection-observer.once="{ rootMargin: '50% 0% 50% 0%' }"
:class="classes"
#has-entered="onEntered" // Above directive emits this
#mouseover.native="play()"
#mouseleave.native="pause()"
>
<video
ref="video"
:src="videoUrl"
:loop="true"
:autoplay="true"
:muted="true"
:playsinline="true"
disablepictureinpicture="true"
preload="none"
crossorigin="anonymous"
#error="onError('video')"
/>
</figure>
<script>
export default {
props: {
videoUrl: {}
},
methods: {
onEntered() {
if (this.$refs.video) {
this.$refs.video.load()
}
},
pause() {
if (this.$refs.video) {
this.$refs.video.pause()
}
},
play() {
if (this.$refs.video && this.$refs.video.paused) {
return this.$refs.video.play()
}
},
}
}
</script>
Here is what happening:
video.play() starts loading video content asynchronously.
video.pause() interrupts video loading because it is not ready yet.
video.play() rejects asynchronously loudly.
here is a proper way to implement paly/pause using promise
<video id="video" preload="none" src="https://example.com/file.mp4"></video>
<script>
// Show loading animation.
var playPromise = video.play();
if (playPromise !== undefined) {
playPromise.then(_ => {
// Automatic playback started!
// Show playing UI.
// We can now safely pause video...
video.pause();
})
.catch(error => {
// Auto-play was prevented
// Show paused UI.
});
}
</script>
Also, be aware of this Chromium Bug
I want to autoplay video in fullscreen. I searched and found out that Fullscreen API can be used to do this and found out that code similar to this can be used.
viewFullScreen.addEventListener("click", function () {
var docElm = document.documentElement;
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
}
else if (docElm.msRequestFullscreen) {
docElm.msRequestFullscreen();
}
else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
}
else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
}
}, false);
This code works fine. But this will need some triggering event like mouse click or some keyboard input. But is it possible playing a video automatically in fullscreen without any triggering event as soon as the html file is opened?
Nope. From MDN:
NOTE: Fullscreen requests need to be called from within an event handler or otherwise they will be denied.
I have copied the Youtube embedded player demo code Youtube embedded player demo code almost verbatim - the changes are inserting console.log so that I can see what is going on.
<!DOCTYPE html>
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
console.log('onPlayerReady');
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
console.log('onPlayerStateChange');
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
</script>
</body>
</html>
This works as intended in Safari and Firefox, that is once the API loads the choosen video plays for six seconds and the console.log contains evidence that the events are firing. However in Internet Explorer the events onReady and onStateChange do not fire. The video will play if I manually click the play control in the player but I do not get the expected onStateChange events.
I checked the version of Flash on my machine and it is: 11.2.202.228 and the current available is 12.0.0.70. If I disable Flash in IE (manage add ons) the player works as expected: plays for 6 seconds and events fire.
I did not update Flash because while that might fix the issue on my machine my audience will be limited to those only that can do the same and if I updated I would not be able to find out if the issue was resolved by other means.
I have also tried to manually create the iframe myself as mentioned in the above Youtube documentation, this worked as expected in Firefox and Safari but not in IE.
There is a Youtube demo player this works perfectly in Firefox, the play, pause and stop controls via the API work and the events are clearly firing as the Events and Function Calls are written to the screen in the "Statistics" section. In contrast when I look at this in IE the play, pause and stop controls via the API do nothing. There is no output in the statistics section. The only way to play and pause is to use the buttons on the player. If I disable Flash then the player & API works perfectly.
If I try an iframe non API embed then the player works correctly, this leads me to believe that it is an issue arising from using the API.
I know some would say that all users should have the latest Flash, easier said than done. Many schools, companies and libraries lock down the options section in IE so the user would be able to turn off Flash for my site.
I wonder is there some way of turning off Flash when using the API?
I have searched around Stackoverflow and elsewhere and while I have found similar issues I have not found any fixes. I did find a somewhat similar promising lead but alas I had no joy. Also here and here and here and here and here.
I must point out that the issue is present when used on a local machine and on a website and even if I set the origin parameter.
I would appreciate suggestions and help. Thanks in advance.
I wonder is there some way of turning off Flash when using the API?
You could force the YouTube player to use its html5 player using the following option:
playerVars: {html5: 1}
Changing your code to:
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
playerVars: {
html5: 1
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
I'm getting different results i Firefox and Chrome when using <audio> and <video> with preload="none" and then trying to play from Javascript.
Let's say i was using preload="auto" or preload="metadata" :
audio.src = "filename";
audio.play();
That seems to work fine in both Firefox and Chrome but i want to use preload="none" and then Chrome dossent play.
So i'm trying this code with preload="none" :
audio.src = url;
audio.load();
audio.addEventListener('canplay', function(e) {
audio.play(); // For some reason this dossent work in Firefox
}, false);
audio.play(); // Added this so Firefox would play
I don't know if that's the correct way to do it.
I'm using :
Firefox 20.0.1
Chrome 25.0.1364.172 m
I made a demo : http://netkoder.dk/test/test0217.html
Edit :
In the 2nd audio player (on the demo page) it seems that when using preload="none" you have to use load().
But is it correct to just use play() right after load() or is the correct way to use an event to wait for the file to load before playing it ?
In the 3rd audio player it seems Firefox 20.0.1 dossent support the canplay event correctly when used with addEventListener() because it dossent trigger after load(), it triggers after play() and also triggers when scrubbing though the sound which dossent seem to be the way the canplay should work.
Using .oncanplay does work.
So the following code seems to work :
function afspil2(url) {
afspiller2.src = url;
afspiller2.load(); // use load() when <audio> has preload="none"
afspiller2.play();
}
function afspil3(url) {
afspiller3.src = url;
afspiller3.load(); // use load() when <audio> has preload="none"
//afspiller3.addEventListener('canplay', function() { // For some reason this dossent work correctly in Firefox 20.0.1, its triggers after load() and when scrubbing
// afspiller3.play();
//}, false);
afspiller3.oncanplay = afspiller3.play(); // Works in Firefox 20.0.1
}
I updated the demo to include the changes : http://netkoder.dk/test/test0217.html
My way of adding addEventListener inside the afspil3() function dossent seem good because the first time the function is called the code inside addEventListener is called 1 time. The second time the function is called the code inside addEventListener is called 2 time and then 3 times and so on.
It's because your audio tags are missing the required src attribute, or <source> tags. When I added them in your demo, all 3 players immediately began working in both Chrome and FF.
Also, I recently discovered that src cannot be an empty string and subsequently changed with JS. If there's a reason you can't set the src in the HTML, your best alternative, IMO, is to create the audio elements with Javascript:
var audio = new Audio();
audio.src = url;
audio.controls = true;
audio.preload = false;
// and so on
Edit: Ok. It seems that in Chrome, when the HTML is preload="none" it is necessary to call load() before playing when the src is changed. Your second audio doesn't preload, so your function needs to be this:
function afspil2(url) {
afspiller2.src = url;
afspiller2.load(); // add load call
afspiller2.play();
}
Then, it seems that in Firefox, it is necessary to set preload="auto"; when attaching an event to the canplay event, like in the 3rd function.
function afspil3(url) {
afspiller3.src = url;
afspiller3.preload = "auto";
afspiller3.load();
afspiller3.addEventListener('canplay', function(e) {
this.play(); // For some reason this dossent work in Firefox
}, false);
}
That just seems very strange, but I tested it multiple times, and each time it would play if preload="auto" is called, but would not play if it isn't called. Note that it wasn't necessary for the 2nd player, which was also preload="none" in the HTML tag.
Finally, Chrome has some odd behaviors if there are multiple <audio> elements on the page. For all three players, reloading the page and clicking "the big electron" link would play correctly.
Reloading and then clicking "Yoda" on the 2nd or 3rd player won't do anything, but it WILL play for the first player. But, if the top player is played first by any means - play button or either link - then the other two "Yoda" links will suddenly work.
Also, if you click a 2nd or 3rd "Yoda" link first after reload, and then click the top player, the previously clicked "Yoda" (that didn't previously play) will begin to play on its own after the top player stops.
Suffice it to say they have some bugs to work out.
The correct way in my opinion would mean using an existing solution, like http://mediaelementjs.com/
If your really interested in the details on the best way to play audio and video with js then look at the source:
https://github.com/johndyer/mediaelement/tree/master/src/js
https://github.com/johndyer/mediaelement/blob/master/src/js/me-mediaelements.js
I am writing a Browser Plugin and need to find a way to get the current time a YouTube Video playing on YouTube using JavaScript. I have been playing around in the Chrome JavaScript Console and haven't had any luck.
It appears that the chrome API only works with embedded video players not a video that is playing on on youtube.com. One option I looked into is in the share section of a video their is an input box for the "start at:" time that contains the current time of the video. I have tried using .value and .text on this input box and they both return undefined? Does anyone have any ideas?
ytplayer = document.getElementById("movie_player");
ytplayer.getCurrentTime();
See the api
Update: if it didn't work, also try player.playerInfo.currentTime (codepen live example)
Depends on what you want
player.getCurrentTime():Number
Returns the elapsed time in seconds since the video started playing.
player.getDuration():Number
Returns the duration in seconds of the currently playing video. Note
that getDuration() will return 0 until the video's metadata is loaded,
which normally happens just after the video starts playing.
http://code.google.com/apis/youtube/js_api_reference.html
Finally I found how to make it work on iOS (and Android too).
Actually, the whole youtube js api was broken for me if run on mobile browser.
Problem solved by creating player using new YT.Player as described in YouTube IFrame API.
Please note: only creating <iframe> from <div> placeholder works for mobile browsers at the time. If you try to use existing <iframe> in new YT.Player call, as mentioned in IFrame API, this will not work.
After player created, it's possible to use player.getCurrentTime() or player.getDuration() with player instance created.
Note: I had no luck calling this methods on player obtained with
player = document.getElementById(...) (from #JosephMarikle answer).
Only created player instance worked in mobile browsers.
Useful links:
YouTube IFrame API
YouTube JavaScript API
YouTube Player Demo
You can use Html5 Video API on youtube.com
var htmlVideoPlayer = document.getElementsByTagName('video')[0];
htmlVideoPlayer.currentTime
Note: It's not gonna work on Youtube Iframe API because Iframes are isolated. You cannot access the context of a Youtube IFrame .
In 2020, this works:
player.playerInfo.currentTime
full code:
see it live on codepen
Just FYI, There is a new iframe API for the YouTube player:
https://developers.google.com/youtube/iframe_api_reference
document.querySelector('video').currentTime
Stop at specific time youtube video and show notification box in GWD
<script>
var yid = document.getElementById("gwd-youtube_1");
var idBox = document.getElementById("box1");
pausing_function = function(event) {
var aa = setInterval(function() {
if (yid.getCurrentTime() > 8.0 && yid.getCurrentTime() < 8.1) {
yid.pause(yid);
idBox.style.opacity = 1;
console.log(yid.getCurrentTime() + "playing")
clearInterval(aa);
yid.removeEventListener("playing", pausing_function);
}
}, 100)
}
yid.addEventListener("playing", pausing_function);
var pausing_function_1 = function() {
if (yid.getCurrentTime() > 8.1) {
console.log(yid.getCurrentTime() + "pause")
// remove the event listener after you paused the playback
yid.removeEventListener("playing", pausing_function);
}
};
</script>
play video and hide notification
<script type="text/javascript" gwd-events="handlers">
window.gwd = window.gwd || {};
gwd.pauseVideo = function(event) {
var idBox = document.getElementById("box1");
idBox.style.opacity = 0;
};
</script>
<script type="text/javascript" gwd-events="registration">
// Support code for event handling in Google Web Designer
// This script block is auto-generated. Please do not edit!
gwd.actions.events.registerEventHandlers = function(event) {
gwd.actions.events.addHandler('gwd-youtube_1', 'playing', gwd.pauseVideo, false);
};
gwd.actions.events.deregisterEventHandlers = function(event) {
gwd.actions.events.removeHandler('gwd-youtube_1', 'playing', gwd.pauseVideo, false);
};
document.addEventListener("DOMContentLoaded", gwd.actions.events.registerEventHandlers);
document.addEventListener("unload", gwd.actions.events.deregisterEventHandlers);
</script>