Autoplay videos on scroll as window focuses - javascript

A video blog where the videos are 800x600 pixels (take most of browser screen).
I am using html tag with attributes 'loop' and 'autoplay'
With more then 10 videos so far, the browser plays all of them a stalls!
I need a piece of code to play one video at a time as the user scrolls to the video in focus and pause the video as soon as out of scroll focus.

If the videos are evenly spaced at 600px high and lets assume the margins on the videos are at least 50px, that means each video has a functional height of 700px. So you want to play the video that is closest to the top of your screen.
const qVideos = document.querySelectorAll('video');
^ This assumes you are using the video element to play videos and is collecting them.
const calcVideoNumber = scrollPositionY => Math.floor( scrollPositionY / 700 );
^This function takes in your scroll position and returns the number of 700px (this should be your video section height) high sections you are down the page.
const stopPlaying = videos => Array.from(videos).forEach( video => video.pause() );
^ Stops playing all of the passed videos
window.onscroll = event => {
stopPlaying(qVideos);
qVideos[calcVideoNumber(event.scrollY)].play()
}

Related

How to change the height of video stream in SignalWire video api

Hi all I am using Signalwire's video calling functionality to make a video calling app. I am facing one issue here, as most of the times, we use video calling through phones or small screen sizes the height of the video is very small there.
Is there any way to increase the height of that div on which the video stream is getting injected?
Here What it looks like-
In mobile my video screen is quite small I want to increase the height.
I tried something like-
$scope.roomObject = new SignalWire.Video.RoomSession({
token: token,
rootElement: document.getElementById('root'), // an html element to display the video
audio: true,
video: {
width: { min: 720},
height: { min: 1280}
}
}
});
This does change the inner video into portrait mode but the issue remains, I can't increase the height.
Note- increasing the height of div not working I can increase the width though.
Thanks
It sounds like you're trying to extend the video canvas vertically to fill the entire screen. While you can change the aspect ratio of your video stream itself (which is how you're swapping your stream to portrait), you can't change the aspect ratio of the whole canvas.

Repeatedly setting HTML5 video's currentTime is jittery on Chrome/Firefox but not Safari

I'm trying to control a video by scrolling so that as the user moves down the page, the video moves with their scrolling. I do this by adding an event handler to the scroll event, which updates the video element's currentTime attribute. When using Safari (11.0.2), the animation is smooth but on Chrome (63) or Firefox, the frame only updates at the end of an inertial scroll. I am able to smooth the animation by lowering the video's horizontal resolution to 600px. Is this simply a product of differing performance or is does my code have some browser-specific optimisation issues?
Note: I tested using a Mac with multitouch smooth scrolling. Not sure if the behaviour is less pronounced with a scroll wheel.
Below is the js used and a link to an example:
var total, video;
window.onload = function() {
video = document.getElementById("video");
// Should react to scrolling until halfway down the video.
total = video.scrollHeight/2 + document.getElementById('top').scrollHeight;
window.addEventListener("scroll", animateGoat, false);
};
function animateGoat(ev) {
var scroll = window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop || 0;
// Updates the video to the time with the same fraction of completion as the scroll.
video.currentTime = scroll <= total ? 2 * scroll / total : 2;
}
https://codepen.io/anon/pen/gVbNNQ
I believe the choppiness has to do with the way the in which the video itself is encoded.
Using ffmpeg, you can manually specify the keyframe interval which specifies the distance between I-frames.
I can't provide insight as to why the videos render smoothly on Safari and not on Chrome / FF, but creating a video with a smaller keyframe / GOP interval alleviates this issue, at the cost of a larger file size.
Try convert your video with the following:
ffmpeg -i input.mp4 -g 4 -vcodec libx264 -profile:v main -level:v 4.1 -an -movflags faststart output.mp4
The key flag in the above being -g 4 which sets a keyframe every 4 frames.

Display same video in multiple <video> tags

I have a video (let's call it composite video) composed by multiple other videos concatenated using some pattern. For example, see the screenshot of the videos below, composed by two and four other videos, respectively:
However, I need to display it differently: One main, larger, video and N-1 video thumbnails, where N is the total number of videos. Here are this other display corresponding to the videos above:
To display the main I'm using a combination of HTML and CSS to position the video I want in the larger div. It runs smoothly, no matter the number of videos in the composite videos.
To display the thumbnails, I'm using <canvas> to draw the parts I want:
video.addEventListener('play', function() {
(function loop() {
drawThumbnails();
setTimeout(loop, 1000 / 30); // drawing at 30fps
})();
}, false);
function drawThumbnails() {
for (var i = thumbs.length - 1; i >= 0; i--) {
drawThumbnail(thumbs[i]);
};
}
function drawThumbnail(thumb) {
var thumbNumber = Number(thumb.id.match(/\d+/g));
var canvasContext = thumb.getContext('2d');
var thumbCoordinates = getVideoCoordinates(thumbNumber);
var srcX = thumbCoordinates.column * videoWidth;
var srcY = thumbCoordinates.row * videoHeight;
canvasContext.drawImage(
video, srcX, srcY, videoWidth, videoHeight, // Source
0, 0, thumb.width, thumb.height); // Destination
}
It was working well for 3 (sometimes 4) videos. However, as the number of videos in the composite video increases, the videos in the thumbnails start to freeze and run not in a smooth way. This is probably happening because there's too much image processing being done at the same time.
I think the proper way to do it is, somehow, using <video> and methods specific for videos, not for images. I've also tried to use the same src in the multiple <video> tags (one for each thumbnail) and add eventListeners to play/pause the videos in the thumbnails once the main video is played/paused. That's not very efficient, particularly because videos can get out of sync sometimes, when seeking/buffering.
Is there a way of using only one video in multiple <video> tags and use only one of them (in my case, the one that contains the main video) to control all the others? In case there's no way of doing that, is there an alternative approach for my problem?
Thanks a lot,
P.S. Having multiple, separated, videos is not an option in my situation. It would take a very long time to process the input video and divide it in multiple videos.
You can certainly reference the same video across multiple video elements. Cloning the original and appending them as thumbnail videos might alleviate some of the tedium.
Iterating over the thumbnails and .play()ing them should be fine so long as you set their currentTime with that of the main video prior to playing, to minimize drift. There may be some need to wait for canplay to fire on the main video and/or the thumbnails depending on the exact experience you're looking to deliver.
If each thumbnail is given a parent container you could possibly position the video element serving as your thumbnail such that only the portion of the video you care to see is visible, clipping the rest.
FWIW, CSS masking might be of interest to you as a performance optimization if it helps the compositing performance.
You will need to manually coordinate playing/pausing all of the video elements, but that should be easy enough to do with a facade object that handle the play pause of all the "linked" video elements.
I know I'm posting late, and you may have already found an answer. However, if anyone else comes across this question, here is my answer:
You can use multiple video elements with the same source. The way to do it is with css.
.wrapper {
height: /*height of one video*/;
width: /*width of one video*/;
overflow: hidden;
}
video {
position: relative;
top: /*height offset*/;
left: /*width offset*/;
}
And HTML
<div class="wrapper">
<video src="myvideo.mp4"></video>
</div>
So, if I was doing the top right video, and each one was 250px by 250px, I would set my wrapper height and width to 250px and my video top to 0px and my video left to 250px
What's the format of the main video? Is it an on demand mp4/webm file?
If you still want to go with your approach of grabbing frames and paint them but is facing performance issues, consider using web workers for the heavy jobs. Here you can find some examples of video/canvas manipulation with web workers.

Controlling the HTML 5 video controller with javascript?

I was wondering how I could control/edit the status bar of an html5 video controller (i think that's what its called.. Its the bar that has your current position in the video)?
What I'm trying to do is create a program that will enable a user to pick part of the video and loop it over and over again.
Once a user hits a button, there will be 2 slider buttons underneath the progress bar, (one for beginning and ending) and the user can select the beginning and ending times by sliding the sliders and having the program highlight the portion they selected.
What I am confused about is how the video element (progress bar) is effected by the java script, and how to make the selection portion of the bar. (the highlighted section)
any help would be awesome.
here are pictures of what I am trying to explain
http://imgur.com/a/XX1e3#0
Thanks Guys
The progress bar of a <video> element isn't really "affected" by JS, per se...
The progress bar is little more than a <div> with coloured <div>s inside (to show progress).
The video object (the JS object) has properties and methods which control playback/position, and fire update events to allow you to react.
<video id="my-video" src="/media/my-video.mp4"></video>
Then the JS properties of that object are pretty straightforward:
var video = document.querySelector("#my-video");
video.play(); // plays
video.pause(); // pauses
video.currentTime; // gets or sets the current playback time
video.duration; // gets the length of the media file
// stop doesn't exist, but it's easy enough to make
video.stop = function () { video.pause(); video.currentTime = 0; };
video.addEventListener("timeupdate", function () {
/* more of the video has played */
});
All you really need to do here, is spend some time thinking about how the bar itself will operate.
If 100% of the bar is equal to 100% of the video's duration, then as users click on other divs to move them around, you can figure out where those cursors are on the time-bar...
eg: your bar is 500px, your video is 300 seconds.
The user sets one cursor to 100px from the left of the bar (100px/500px == 0.2 * 300s == 60s).
So now you know that when you loop back around, you're going to set video.currentTime = 60;.
How do you know when to do that?
video.addEventListener("timeupdate", function () {
var end_percent = end_cursor_pos_px / bar_width_px,
start_percent = start_cursor_pos_px / bar_width_px,
end_time = video.duration * end_percent;
if (video.currentTime > end_time) {
/* set video.currentTime to the "start_time" */
video.currentTime = video.duration * start_percent;
}
});
As a performance consideration, you should update the start-percent/end-percent whenever the user moves either cursor.
Then, when the video tells you it's played more, you should calculate the percentage of the duration.
Note that you might not have the full duration available to you until the video has played a bit (or until it's played all the way through, even).
There is a "loadedmetadata" event you can listen to, that will tell you when you've got it all.
But still, if only 30 seconds have loaded, 50% of 30 seconds is still 50%.
If you want that not to be the way it works, then you need to work around it, either by waiting for that event (even if the video has to be 100% done), or by sending it in a JSON request, or reading it from headers in an AJAX "HEAD" request.
The math for the positions of the cursors and bar is pretty easy, too.
As long as you guarantee that the cursors stay within the bounds of the bar, (and the user hasn't scrolled the window to the point where part of the bar is off-screen, left or right), it's just going to be:
var start_cursor = startEl.getBoundingClientRect(),
end_cursor = endEl.getBoundingClientRect(),
scrub_bar = scrubEl.getBoundingClientRect();
var start_percent = (start_cursor.left - scrub_bar.left) / scrub_bar.width,
end_percent = (end_cursor.left - scrub_bar.left) / scrub_bar.width;
If they have scrolled left/right, then you just want to add window.pageXOffset to those values.
There isn't much more to it.
Just be sure to update the start/end percent any time a cursor gets moved, and check your playback percentage against the end-percent, and update the currentTime, any time it's gone past (the callbacks happen several times a second).
Hope that helps a little.

HTML5: Manually "playing" a video inside a canvas, in IOS Safari

So, here's what I'm trying to do:
I want to load a video into a video element, but not have it played in the "normal" way.
Using a timed interval, calculated according to the movie's framerate, I want on each iteration to
A. Manually advance the video one 'frame' (or as close as possible to that).
B. Draw that frame into a canvas.
Thereby having the video "play" inside the canvas.
Here's some code:
<video width="400" height="224" id="mainVideo" src="urltovideo.mp4" type="video/mp4"></video>
<canvas width="400" height="224" id="videoCanvas"></canvas>
<script type="text/javascript">
var videoDom = document.querySelector("#mainVideo");
var videoCanvas = document.querySelector("#videoCanvas");
var videoCtx = null;
var interval = null;
videoDom.addEventListener('canplay',function() {
// The video's framerate is 24fps, so we should move one frame each 1000/24=41.66 ms
interval = setInterval(function() { doVideoCanvas(); }, 41.66);
});
videoDom.addEventListener('loadeddata',function() {
videoCtx = videoCanvas.getContext('2d');
});
function doVideoCanvas() {
videoCtx.drawImage(videoDom,0,0);
//AFAIK and seen, currentTime is in seconds
videoDom.currentTime += 0.0416;
}
</script>
This works perfectly in Google Chrome, but it doesn't work in an Iphone's Safari;
The video frames does not get drawn at all to the canvas.
I made sure that:
The video events I hooked into does get triggered (did 'alerts' and they were shown).
I have control over the canvas (did a 'fillRect' and it filled).
[I also tried specifying dimensions in the drawImage - it didn't help]
Is drawImage with a video object not applicable at all in Iphone Safari...?
Even if I'll manage to find a way to capture the video frames, there are also some other issues in the Iphone's browser:
Access to the currentTime property of the video is only granted once the video has started playing (in a standard way). I thought about maybe somehow "playing it hidden" and then capturing, but didn't manage to do that. Also thought of maybe somehow start playing the video and then immediately stop it;
There doesn't seem to be any way to forcefully start the playing of a video in the IOS Safari. video.play(), or autoplay doesn't seem to do anything. Only if the user taps the "play circle" on the video then the video starts playing (taking all over the screen, as usually with videos on the IPhone's browser).
Once the video plays - the currentTime property does get forwarded. On the video itself. When you pause the video and go back to the normal html page - you can see the frames on the video element changing. Though, in a slow phase (unlike in Google Chrome, where the rate seems to be smooth enough to make it look like it's playing) - in the iphone it looks to be a rate of something like 2-3 frames per second maybe. It stays the same even if I try changing the interval timing, I guess there's a minimum time limit that the browser on the IPhone can handle.
"Bonus question" :)
- When the frames on the video element progresses from the event - the circle "play button" is visible on the video element (since it is not actually 'playing'). Is there anyway to hide it and make it invisible?
This has been tested on Iphone 3GS (with both 3G and Wifi) and Iphone 4, both running IOS 5, both having the same results as described.
Unfortunately I don't have an iOS device to test this, but I don't think you need to actually capture the video frames in the way that you're attempting using the currentTime property. The usual process looks something like this:
create a video element without controls (omit the controls attribute)
hide the element
when the video is playing draw it to the canvas on an interval
As you've discovered, iOS devices do not support autoplay on HTML5 video (or indeed audio) but you can create a separate control to initiate playback of the video using the play() method.
This approach should solve the issue you're having with the play button being visible since in this case you are actually playing the video.
I don't believe the loadeddata event is called on iOS, try the loadedmetadata event instead. I also found it necessary on iOS to call the videoDom.load() method after setting videoDom.src.
For my use case, I need to do a "dRAF" (double requestAnimationFrame) after the seeked event to ensure something was actually drawn to the canvas rather than a transparent rectangle.
Try something like:
videoDom.onloadedmetadata = () => {
videoCanvas.height = videoDom.videoHeight
videoCanvas.width = videoDom.videoWidth
videoDom.currentTime = 0
}
videoDom.onseeked = () => {
// delay the drawImage call, otherwise we get an empty videoCanvas on iOS
// see https://stackoverflow.com/questions/44145740/how-does-double-requestanimationframe-work
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
videoCanvas.getContext('2d').drawImage(videoDom, 0, 0, videoCanvas.width, videoCanvas.height)
videoDom.currentTime += 0.0416
})
})
}
videoDom.load()
From this gist

Categories