How to pause embedded youtube video on the last frame? - youtube

When playing embedded youtube videos, I don't want to display a poor resolution thumbnail when completed. Instead I'd like to have a high-resolution image of the last frame. Is this possible?
Either I can pause on the last frame or maybe there's a way to create a high-resolution thumbnail of the last frame?

You can use the YouTube Player API to detect when playback has ended, and at that point do some DOM manipulation to swap out the player and swap in an img tag pointing to one of the video's thumbnails. There normally would not be a thumbnail for the last frame in the video, though, so you would have to choose a different thumbnail.
If you just want to disable the related videos that are shown when a video ends, you can use the rel=0 player parameter, but that will leave you with a black screen.

Video player gives a callback "onStateChange" with the status YT.PlayerState.PAUSED before calling the callback with status YT.PlayerState.ENDED, you can try here.
I am also doing the same thing. I am just displaying an overlay to cover the video with replay button, But still it appears at the end of the video and then overlay appears (some glitch is observed)

I ran into this same problem and after spending a bunch of time playing around with it, the best solution for me was to just pause the video a fraction of second before the end, thus keeping it on that frame. Something like this:
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
playerVars: { 'autoplay': 1, 'controls': 0,'loop': 1, 'showinfo': 0, 'disablekb': 1, 'version': 3, 'vq': 'large', 'wmode':'opaque','rel': 0 },
videoId: 'YOURVIDEOID',
events: {
'onReady': onPlayerReady,
'onStateChange': onStateChange,
'onytplayerStateChange': onStateChange }
});
}
var done = false;
function onStateChange(event){
var videoDuration = player.getDuration();
if (event.data == YT.PlayerState.PLAYING && !done) {
// pause 0.1 seconds before the end
setTimeout(pauseVideo, (videoDuration-0.1)*1000);
done = true;
}
}
function pauseVideo() {
player.pauseVideo();
}
After the video starts (which we catch using onStateChange), we can get the video duration from the YouTube Player API (using player.getDuration), and then use setInterval to time a function that pauses the video 0.1 seconds (or whichever value we choose) before the end.

After running into to this issue too I ended up with this, which has been working pretty consistent for me so far:
var player;
var curTimer;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
videoId: 'The Video ID',
playerVars: { 'rel':0, 'enablejsapi':1, 'autohide':1, 'wmode':'opaque'}
});
player.addEventListener('onReady', 'onPlayerReady');
player.addEventListener('onStateChange', 'onPlayerStateChange');
}
function onPlayerStateChange(event) {
var videoDuration = event.target.getDuration();
var curTime = event.target.getCurrentTime();
if ( curTime ) {
/** Video Already Started */
/** Get video time remaining. */
videoDuration = videoDuration-curTime;
/** Remove old 'setTimeout' function */
removeTimeout(curTimer);
}
/**
Note: The '.25' is time subtracted to stop video on last frame.
Adjust this as needed.
*/
/** Add/Re-Add 'setTimeout' function with video time remaining. */
curTimer = setTimeout(pauseVideo, (videoDuration-.25)*1000);
}
function removeTimeout(elTimer){
/** Remove old timer */
clearTimeout(elTimer);
}
function stopVideo() {
player.stopVideo();
}
Explanations and/or notes are within the comments.

var thumb = true;
function onStateChange(event) {
if (thumb && event.data == YT.PlayerState.PLAYING) {
thumb = false;
player.pauseVideo();
return;
}
if (event.data != YT.PlayerState.ENDED) {
return;
}
player.setPlaybackQuality('highres');
player.seekTo(player.getDuration() - 1, true);
player.playVideo();
thumb = true;
}

Related

Is there a way to autoplay a YouTube video when it has scrolled into viewport and pause when scrolled out?

I am using ACF flexible fields and I have added a field for the Youtube link in each block. When I scroll to each block, I would like the Youtube iframe to start playing automatically (so when it has reached the viewport). When I scroll out of this block I would then like it to pause, and when it's reached the next block it will play that, etc. Please could someone assist?
The main problem is that YouTube doesn't allow to autoplay the videos without a user interaction... you can read the iframe api reference
I tried with this JavaScript function:
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
This function returns 'true' if the passed element is in the viewport and false if not in.
Then I use another function called in each addEventListener for 'scroll' and 'resize' events:
function checkScroll() {
// Get the video element
var video = document.getElementById('player');
if (isInViewport(video) == true) {
playVideo();
} else {
pauseVideo();
}
};
But like I say at the beginning, this only works if user initiates the player by an interaction.
Hope it helps.

Is there a way to use one background video for multiple slides in Reveal.js?

I am trying to create some slides using Reveal.js using a background video that spans multiple slides. I have no problems getting a background video to work on one slide using the data-background-video attribute
The following is the working snippet from the reveal.js documentation:
<section data-background-video="https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.mp4,https://s3.amazonaws.com/static.slid.es/site/homepage/v1/homepage-video-editor.webm" data-background-video-loop>
<h2>Video. Multiple sources can be defined using a comma separated list. Video will loop when the data-background-video-loop attribute is provided.</h2>
</section>
What I want however is for the video to be continuous even when I move to the next slide. If I put data-background-video in a "parent" , I can see the first frame of the video but it just freezes there
<section data-background-video="...">
<section><h1>slide 1</h1></section>
<section><h1>slide 2</h1></section>
<section><h1>slide 3</h1></section>
</section>
I want to use this as a backdrop for a performance where I want the lyrics overlaid on background video. I will switch background videos for for a certain part of the song but want the video to be the same while lines of the lyrics appear.
Here's a way to do this. Inside reveal.js modify the updateBackground() function:
Change the "if( currentBackground )" block from:
if( currentBackground ) {
// Start video playback
var currentVideo = currentBackground.querySelector( 'video' );
if( currentVideo ) {
var startVideo = function() {
currentVideo.currentTime = 0;
currentVideo.play();
currentVideo.removeEventListener( 'loadeddata', startVideo );
};
if( currentVideo.readyState > 1 ) {
startVideo();
}
else {
currentVideo.addEventListener( 'loadeddata', startVideo );
}
}
//................
}
to:
if( currentBackground ) {
// Start video playback
var currentVideo = currentBackground.querySelector( 'video' );
// This might be a nested node - check for parent video
if(!currentVideo) currentVideo = currentBackground.parentNode.querySelector( 'video' );
if( currentVideo ) {
var startVideo = function() {
//currentVideo.currentTime = 0;
currentVideo.play();
currentVideo.removeEventListener( 'loadeddata', startVideo );
};
if( currentVideo.readyState > 1 ) {
startVideo();
}
else {
currentVideo.addEventListener( 'loadeddata', startVideo );
}
}
//................
}
What I suggest is to search for video background in the parent node () as well and to resume it.
Just found this answer via google and it did not work. So I started to find my own solution and it seams to be quite simple. Just add this code below the "Reveal.initialize" command on the bottom of your index html (inside the script tag):
Reveal.addEventListener( 'slidechanged', function( event ) {
window.setTimeout(function(){
var stackedBackgrounds = $('body > div > div.backgrounds > div.stack');
stackedBackgrounds.each(function(){
var me = $(this);
var video = me.find('video').get(0);
console.log(video);
if(video){
if(me.hasClass('present')) {
console.log('play');
video.play();
}
else {
console.log('stop');
video.load();
}
}
});
}, 300);
} );
What does it do?
It registers the "slidechanged"-Event, waits another 300ms (because the event triggers too early). Then it finds all stacked backgrounds and starts the video if it is visible, otherwise it stops the video by reloading. That also sets the resume to position 0.
That all.
PS.: Of cause you can drop the console.log lines.

Very slow hover interactions in OpenLayers 3 with any browser except Chrome

I have two styles of interactions, one highlights the feature, the second places a tooltop with the feature name. Commenting both out, they're very fast, leave either in, the map application slows in IE and Firefox (but not Chrome).
map.addInteraction(new ol.interaction.Select({
condition: ol.events.condition.pointerMove,
layers: [stationLayer],
style: null // this is actually a style function but even as null it slows
}));
$(map.getViewport()).on('mousemove', function(evt) {
if(!dragging) {
var pixel = map.getEventPixel(evt.originalEvent);
var feature = null;
// this block directly below is the offending function, comment it out and it works fine
map.forEachFeatureAtPixel(pixel, function(f, l) {
if(f.get("type") === "station") {
feature = f;
}
});
// commenting out just below (getting the feature but doing nothing with it, still slow
if(feature) {
target.css("cursor", "pointer");
$("#FeatureTooltip").html(feature.get("name"))
.css({
top: pixel[1]-10,
left: pixel[0]+15
}).show();
} else {
target.css("cursor", "");
$("#FeatureTooltip").hide();
}
}
});
I mean this seems like an issue with OpenLayers-3 but I just wanted to be sure I wasn't overlooking something else here.
Oh yeah, there's roughly 600+ points. Which is a lot, but not unreasonably so I would think. Zooming-in to limit the features in view definitely helps. So I guess this is a # of features issue.
This is a known bug and needs more investigation. You can track progress here: https://github.com/openlayers/ol3/issues/4232.
However, there is one thing you can do to make things faster: return a truthy value from map.forEachFeatureAtPixel to stop checking for features once one was found:
var feature = map.forEachFeatureAtPixel(pixel, function(f) {
if (f.get('type') == 'station') {
return feature;
}
});
i had same issue, solved a problem by setInterval, about this later
1) every mouse move to 1 pixel fires event, and you will have a quee of event till you stop moving, and the quee will run in calback function, and freezes
2) if you have an objects with difficult styles, all element shown in canvas will take time to calculate for if they hit the cursor
resolve:
1. use setInterval
2. check for pixels moved size from preview, if less than N, return
3. for layers where multiple styles, try to simplify them by dividing into multiple ones, and let only one layer by interactive for cursor move
function mouseMove(evt) {
clearTimeout(mm.sheduled);
function squareDist(coord1, coord2) {
var dx = coord1[0] - coord2[0];
var dy = coord1[1] - coord2[1];
return dx * dx + dy * dy;
}
if (mm.isActive === false) {
map.unByKey(mm.listener);
return;
}
//shedules FIFO, last pixel processed after 200msec last process
const elapsed = (performance.now() - mm.finishTime);
const pixel = evt.pixel;
const distance = squareDist(mm.lastP, pixel);
if (distance > 0) {
mm.lastP = pixel;
mm.finishTime = performance.now();
mm.sheduled = setTimeout(function () {
mouseMove(evt);
}, MIN_ELAPSE_MSEC);
return;
} else if (elapsed < MIN_ELAPSE_MSEC || mm.working === true) {
// console.log(`distance = ${distance} and elapsed = ${elapsed} mesc , it never should happen`);
mm.sheduled = setTimeout(function () {
mouseMove(evt);
}, MIN_ELAPSE_MSEC);
return;
}
//while multithreading is not working on browsers, this flag is unusable
mm.working = true;
let t = performance.now();
//region drag map
const vStyle = map.getViewport().style;
vStyle.cursor = 'default';
if (evt.dragging) {
vStyle.cursor = 'grabbing';
}//endregion
else {
//todo replace calback with cursor=wait,cursor=busy
UtGeo.doInCallback(function () {
checkPixel(pixel);
});
}
mm.finishTime = performance.now();
mm.working = false;
console.log('mm finished', performance.now() - t);
}
In addition to #ahocevar's answer, a possible optimization for you is to utilize the select interaction's select event.
It appears that both the select interaction and your mousemove listener are both checking for hits on the same layers, doing double work. The select interaction will trigger select events whenever the set of selected features changes. You could listen to it, and show the popup whenever some feature is selected and hide it when not.
This should reduce the work by half, assuming that forEachFeatureAtPixel is what's hogging the system.

youtube api cannot change video quality

My problem was, changing quality during video playing.
When i used setPlaybackQuality in onReady event function, quality was changed, but during video playing it was not possible. Although onPlaybackQualityChange event returns quality which i wanted but in fact didn't changed video quality.
player[playaID] = new YT.Player('content_'+playaID, {
height: playaHeight,
width: playaWidth,
videoId: playaVID,
playerVars: {
'autoplay':0,
'controls':0,
'showinfo':0,
'fs': 1,
'rel': 1,
'modestbranding':0
},
events: {
'onReady': onPlayerReady(playaID),
'onStateChange':onPlayerStateChange(playaID,event),
'onPlaybackQualityChange':onPlayerQualityChange(playaID,event)
}
});
SOLLUTION:
If you want to change quality during video playing:
1: you must stop video player.stopVideo()
2: set quality - player.setPlaybackQuality('hd720')
3: play video - player.playVideo()
below is mine function for setting quality.
var setQuality=function(playaID,q){
var curSeek = player[playaID].getCurrentTime(); // current video time
var status = player[playaID].getPlayerState(); // video status
if(status!=-1 && status!=5){ // if not started, does not to be stoped
player[playaID].stopVide();
}
player[playaID].setPlaybackQuality(q);
player[playaID].seekTo(curSeek);
if(status!=1){
player[playaID].paseVideo(); // if was paused
}else{
player[playaID].playVideo(); // if was playing
}
}

YouTubePlayer isn't playing a video

I used YouTubePlayerSupportFragment to play a YouTube video, but it plays the video for 2 or 3 second and stops automatically. I can't find what's wrong.
Below is my code for fragment translation
mYoutubePlayerFragment = new YouTubePlayerSupportFragment();
mYoutubePlayerFragment.initialize(
"key",
mListnnerYouTube);
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragmentTransaction.replace(R.id.fragment_youtube_player,
mYoutubePlayerFragment);
fragmentTransaction.commit();
and my onInitializationSuccess
#Override
public void onInitializationSuccess(Provider arg0, YouTubePlayer player,
boolean wasRestored) {
// TODO Auto-generated method stub
activeplayer = player;
activeplayer.setPlayerStyle(YouTubePlayer.PlayerStyle.DEFAULT);
if (!wasRestored) {
// player.cueVideo("nCgQDjiotG0");
player.loadVideo("nCgQDjiotG0");
}
I used YouTubePlayerSupportFragment in fragment
error message
YouTube video playback stopped due to unauthorized overlay on top of player. The YouTubePlayerView is obscured by com.viewpagerindicator.CirclePageIndicator{420b9758 V.ED.... ......I. 0,412-720,465 #7f0a005c app:id/indicator}. The view is inside the YouTubePlayerView, with the distance in px between each edge of the obscuring view and the YouTubePlayerView being: left: 0, top: 412, right: 0, bottom: 0..
You're most likely displaying something on top of the player view, which is forbidden by the YouTube SDK.
Check the logs, you should see a warning from the YouTube SDK that specifies why the video playback has been stopped. If a View is covering part of the player, the log will specify its ID and its position on the screen.

Resources