I want to detect the end of a Youtube video I am watching on the Youtube website with Javascript.
I know they have an API for that, I tried to loaded it in the page with GreaseMonkey, but it doesn't seem to work. Maybe the API is only for embeded videos on other websites?
Here is what I have so far:
if (window.location.href.indexOf("&enablejsapi=1") == -1)
{
window.location = window.location.href + "&enablejsapi=1";
}
var api = document.createElement('script');
api.type = 'text/javascript';
api.src = "https://www.youtube.com/iframe_api";
document.body.appendChild(api);
var onFinish = document.createElement('script');
onFinish.type = 'text/javascript';
var onFinishContent = "function onPlayerStateChange(event) { console.log('test'); if (event.data == YT.PlayerState.ENDED) { console.log('end'); } }"
var textNode = document.createTextNode(onFinishContent);
onFinish.appendChild(textNode);
document.body.appendChild(onFinish);
function onPlayerStateChange(event)
{
console.log("test");
if (event.data == YT.PlayerState.ENDED)
{
console.log("end");
}
}
Like you can see, I tried to make the onPlayerStateChange function callable in two ways, one directly with Greasemonkey, and the second one by injecting a script node. Both of them doesn't work, I never see the "test" message I display in the console.
Does anyone know why I can't seem to be able to load the API?
Sebastian Simon's comment-answer in the year 2015:
document.querySelector("video").addEventListener("ended",
() => alert("Video ended!")});
I checked that this still works in the year 2022.
Related
I am new to CS and currently working on personal projects. I am creating a web app where a user can copy and paste a YouTube URL to a video in a search box and then have the ability to watch the video on the same web page after clicking watch. To get the video ID, I have used a regex:
function getId(url) {
const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
const match = url.match(regExp);
return (match && match[2].length === 11)
? match[2]
: null;
}
And this is how I am getting the user input:
var inputVal = document.getElementById("userInput").value;
To get the video ID, I am calling getId on the inputVal from the user:
var newVideoId = getId(inputVal)
Below is the YouTube API used to play a video on my page. However, I am finding it difficult to passing in the newVideoId variable to the API under the player object.
// 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: 'newVideoId', //the problem lies here
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
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) {
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
}
The problem is that, nothing shows on my screen after clicking the button. I am able to console.log the ID; that means I am getting the correct ID from the regex equation.
I would appreciate any help on how to use a dynamic ID gotten from user in the API! Thanks
To play a video with the iframe api providing a video Id you can use this function:
loadVideoById({'videoId': 'bHQqvYy5KYo',
'startSeconds': 5,
'endSeconds': 60,
'suggestedQuality': 'large'});
From IFrame Player API:
This function loads and plays the specified video.
The required videoId parameter specifies the YouTube Video ID of the
video to be played. In the YouTube Data API, a video resource's id
property specifies the ID. The optional startSeconds parameter accepts
a float/integer. If it is specified, then the video will start from
the closest keyframe to the specified time. The optional endSeconds
parameter accepts a float/integer. If it is specified, then the video
will stop playing at the specified time.
This should do the trick.
I create websocket server in python to handle notification event. Now, i can receive notification, the problem is i can't play sound because new autoplay policy changed, if i play sound using javascript it give me domexception. Any suggestion please ?
As i know, playing sound is simple in html-javascript. like this example: https://stackoverflow.com/a/18628124/7514010
but it depend to your browsers and how you load and play, so issues is:
Some of browsers wait till user click something, then let you play it (Find a way for it)
In some case browsers never let you play till the address use SSL (means the HTTPS behind your url)
The loading be late so the playing be late / or even not start.
So i usually do this:
HTML
<audio id="notifysound" src="notify.mp3" autobuffer preload="auto" style="visibility:hidden;width:0px;height:0px;z-index:-1;"></audio>
JAVASCRIPT (Generally)
var theSound = document.getElementById("notifysound");
theSound.play();
And the most safe if i want sure it be played when i notify is :
JAVASCRIPT (In your case)
function notifyme(theTitle,theBody) {
theTitle=theTitle || 'Title';
theBody=theBody || "Hi. \nIt is notification!";
var theSound = document.getElementById("notifysound");
if ("Notification" in window && Notification) {
if (window.Notification.permission !== "granted") {
window.Notification.requestPermission().then((result) => {
if (result != 'denied') {
return notifyme(theTitle,theBody);
} else {
theSound.play();
}
});
} else {
theSound.play();
try {
var notification = new Notification(theTitle, {
icon: 'icon.png',
body: theBody
});
notification.onclick = function () {
window.focus();
};
}
catch(err) {
return;
}
}
} else {
theSound.play();
}
}
(and just hope it be played. because even possible to volume or some customization make it failed.)
to bypass new autoplay policy :
create a button that can play the sound, hide it and trigger the sound with :
var event = new Event('click');
playBtn.dispatchEvent(event);
EDIT
assuming you have :
let audioData = 'data:audio/wav;base64,..ᴅᴀᴛᴀ...'; // or the src path
you can use this function to trigger whenever you want without appending or create element to the DOM:
function playSound() {
let audioEl = document.createElement('audio');
audioEl.src = audioData;
let audioBtn = document.createElement('button');
audioBtn.addEventListener('click', () => audioEl.play(), false);
let event = new Event('click');
audioBtn.dispatchEvent(event);
}
usage :
just playSound()
EDIT 2
I re test my code and it does'nt work hum ... weird
So I've got a small app with two panels. Using the iframe API. Clicking on one panel will expand the panel full screen, as well as showing a 'play video' button with some additional information. Clicking a button in the top left will return the UI to it's standard state, closing down the video and shrinking the panels back to fit 50/50.
Now as we've got two videos, I've defined the videos as such, #vidPlayer2 being the second trigger.
$('#vidPlayer1').on('click', function(){
player = new YT.Player('player', {
height: '100%',
width: '100%',
videoId: '(video id here)',
controls: 0,
showinfo: 0,
autoplay: 0,
rel: 0,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
Similarly, we've got the default demo code, with a small modification:
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
done = true;
}
if (event.data == YT.PlayerState.ENDED) {
resetView();
}
}
function stopVideo() {
player.stopVideo();
}
Then, we're trying to get the button to work. In some circumstances, not having clicked one of the vidPlayer buttons, no player is defined, so I threw in an if statement with some validation.
var resetView = function() {
// If a Youtube player is active, make sure we stop it.
if (player === undefined || !player || null) {
console.log("Player could not be found.");
} else {
player.stopVideo();
player.destroy();
}
// Additional code to reset the UI removed below. Works no matter what if the above code is removed.
};
Now for the most part, things work well UNTIL I try to go into a panel, play a video, reset UI, then try to enter and leave the next panel without playing a video. When I follow this exact series of steps, regardless of what panel starts first, I get a TypeError: this.a is null in console. I would've assumed that the validation would've done the trick, but apparently not.
So what I can distinguish from this is it works fine when initialized - i.e. var player is initialized. The return button works through just going back and forth without playing a video. The return button works when a video is actively playing, but the function fails if we try to use the return directly after the player is stopped and destroyed. It does work if we simply pop open another video, however.
Is there something I'm missing when I'm trying to reset the view? Does the youtube player have to be reinitialized? Any help or thoughts would be greatly appreciated. Thanks!
Edit: This is the note that's being thrown by the console. Something to note is main.js:44:5 is the player.stopVideo(); call, and main.js:70:3 is when resetView(); is called on a button click.
TypeError: this.a is null
www-widgetapi.js:120:73
f.C
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:120:73
V
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:112:97
Nb/</this[a]
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:130:124
resetView
file:///Users/cipher/Desktop/ERHS_video/js/main.js:44:5
<anonymous>
file:///Users/cipher/Desktop/ERHS_video/js/main.js:70:3
dispatch
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:10264
add/q.handle
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:8326
The problem here is player is NOT undefined. What's happening is you have a global player reference, and you're doing the following with it:
Creating a player in the first panel
Destroying it when the first panel closes
Calling player.stopVideo() on the already destroyed player (from the first panel) when the second panel closes
Currently, player holds a reference to whatever the last YouTube player you were using is, even if that player has already been destroyed.
What you should be doing is clearing out your reference to the player when you destroy it. Destroy won't (and can't) do that. You can also simplify your if condition since !player will check for null and undefined on its own:
var resetView = function() {
// If a Youtube player is active, make sure we stop it.
if (!player) {
console.log("Player could not be found.");
} else {
player.stopVideo();
player.destroy();
player = null; // Clear out the reference to the destroyed player
}
Highly inspired from IkeDoud's above answer and some others like this one, one year later, here is my way to avoid other video suggestions on play end in embedded API player:
<div id="player"></div>
<style>
#player{
min-width:auto;
min-height:auto;
}
</style>
<script>
// Load 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);
// Initialise the player with options
var player;
var playerOptions = {
videoId: 'ViDeOiD_HeRe',
events: {
'onStateChange': onPlayerStateChange
}
};
// Load the video whern player ready
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', playerOptions);
}
// On video end, reset the player back to poster image
function onPlayerStateChange(event) {
if(event.data === 0) {
$(PaReNt_sElEcToR).find("#player").replaceWith('<div id="player"></div>');
player = null;
player = new YT.Player('player', playerOptions);
}
}
</script>
No more irrelevant suggestions playable from the embedded video container.
And no autoplay (mobile forbids it anyway) to have the poster (thumbnail) image.
On event.data === 0, which is video end... Destroy and reload the Iframe.
I have a template I'm building out using fullPage js to create sliding sections. The page supports video, but scroll and gestures are disabled when users scroll with the cursor on top of the video itself. Likewise, this behavior is observed on mobile.
To work around this, I'm using an empty div overlaying the youtube iframe. This solves the scrolling behavior, but I also lack the ability to directly control the youtube player. I've tried using the youtube API and jquery to fake being able to toggle play/pause with the empty div I have on top of the youtube player, but it's not working.
http://codepen.io/lumpeter/pen/XpayeB
The primary code for youtube is:
function onYouTubePlayerReady(playerId) {
ytplayer = document.getElementById("player");
ytplayer.addEventListener("onStateChange", "onPlayerStateChange");
}
function onPlayerReady(event) {
$('#video-button').click(function(event){
ytplayer.playVideo();
});
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
$('#video-button').click(function(event){
ytplayer.pauseVideo();
});
}
else {
$('#video-button').click(function(event){
ytplayer.playVideo();
});
}
}
Can anybody tell why this isn't working? I have event listeners looking for changes and using a boolean to detect the state of the video to toggle play/pause, but for some reason this doesn't work like I anticipated.
===============UPDATE=================
I've also tried the following:
$(document).on('click', '#pauseVideo', function(){
player = new YT.Player('myVideo')
if (event.data == player.PlayerState.PAUSED) {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}','*');
}
if (event.data == player.PlayerState.PLAYING) {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
}
});
However, I get the error "Uncaught ReferenceError: YT is not defined."
As fullpage.js already does part of the job by adding ?enablejsapi=1 and loading the JS script of the API for you, then you only have to do this:
var player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('myVideo');
}
$(document).on('click', '#pauseVideo', function(){
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
});
Reproduction online
Thanks Alvaro! Yeah I was overthinking the solution a bit with the youtube api. I just needed to use a boolean with a binary true/false statement. Super easy, should have seen it before. Again thanks for your help!
var player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('myVideo',{
events: { 'onStateChange': onPlayerStateChange }
});
}
var isPlaying = true;
$(document).on('click', '#pauseVideo', function(){
if ( isPlaying == true){
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
isPlaying = false;
} else {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}','*');
isPlaying = true;
}
});
I am using flexslider plugin in order to show a picture and video. The slides are changed automatically and it's fine with me. The problem is, that when user plays the video (iframe from youtube), flexslider continues to change slides.
I've checked the documentation and found pauseOnHover, which kinda does the trick, but i'd much rather prefer it to pause when video is started and continue when video is paused/has ended. Any suggestions on how to handle this?
EDIT
Here's the div where iframe is opened:
<div class="span6 animated fadeInLeftBig" id="main-media">
<iframe width="560" height="315" src="https://www.youtube.com/embed/someID?version=3&enablejsapi=1" frameborder="0" allowfullscreen id="iframe-id"></iframe>
</div>
The best way to solve this, is to use YouTube API, somethnig like this:
//Implement Youtube API
(function(){
var s = document.createElement("script");
s.src = "http://www.youtube.com/player_api"; /* Load Player API*/
var before = document.getElementsByTagName("script")[0];
before.parentNode.insertBefore(s, before);
})();
var YT_ready=(function(){var b=[],c=false;return function(a,d){if(a===true){c=true;while(b.length){b.shift()()}}else if(typeof a=="function"){if(c)a();else b[d?"unshift":"push"](a)}}})();
YT_ready(function() {
(function($) {
var frameID = "yourIframeID"
var player = new YT.Player(frameID, {
events: {
"onStateChange": function(event) {
if (event.data == 1 || event.data == 3) {
$('.flexslider').flexslider("pause");
}
else if (event.data == 0 || event.data == 2 || event.data == 5) {
$('.flexslider').flexslider("play");
}
}
}
});
});
})(jQuery);
function onYouTubePlayerAPIReady() {
YT_ready(true)
}