Need to play the next video, when current video time reaches the given value, but don't know why the event that should get currentTime is not firing at all? Maybe someone has an idea why (obviously the simplest tasks are the ones making most problems)
function setupVideoPlayer() {
var currentVideo = 1;
// i.e. [video url, start time, end time]
var videos = [["1.mp4", 0, 4], ["2.mp4", 3, 7]];
var videoPlayer = document.getElementById('videoPlayer');
// set first video clip
videoPlayer.src = videos[0][0]; // 1.mp4
// Syncrhonization function should come here
videoPlayer.addEventListener('timeupdate', function(e){
if (this.currentTime == videos[currentVideo-1][2]) {
currentVideo += 1;
this.src = videos[currentVideo-1][0];
this.play();
}
}, true);
// It makes sure the seek will happen after buffering
// the video
videoPlayer.addEventListener('progress', function(e) {
// Seek to start point
this.currentTime = videos[currentVideo-1][1];
}, false)
}
I would suggest changing the time check to a > rather than = because the currentTime event isn't an integrer
if (this.currentTime == videos[currentVideo-1][2]) {
becomes
if (this.currentTime > videos[currentVideo-1][2]) {
(or you could convert it to an integrer for the test)
If there are other issues it's worth adding a console.log(this.currentTime) to the event just to see if it's triggering the code
Related
I'd like to build an event handler to deal with each new frame of an HTML 5 Video element. Unfortunately, there's no built in event that fires for each new video frame (the timeupdate event is the closest but fires for each time change rather than each video frame).
Has anyone else run into this same issue? Is there a good way around it?
There is an HTMLVideoElement.requestVideoFrameCallback() method that is still being drafted, and thus neither stable, nor widely implemented (it is only in Chromium based browsers), but which does what you want, along with giving many other details about that frame.
For your Firefox users, this browser has a non standard seekToNextFrame() method, which, depending on what you want to do you could use. This won't exactly work as an event though, it more of a way to, well... seek to the next frame. So this will greatly affect the playing of the video, since it won't respect the duration of each frames.
And for Safari users, the closest is indeed the timeupdate event, but as you know, this doesn't really match the displayed frame.
(async() => {
const log = document.querySelector("pre");
const vid = document.querySelector("video");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
if( vid.requestVideoFrameCallback ) {
await vid.play();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp, frame) => {
log.textContent = `timestamp: ${ timestamp }
frame: ${ JSON.stringify( frame, null, 4 ) }`;
ctx.drawImage( vid, 0, 0 );
vid.requestVideoFrameCallback( drawingLoop );
};
vid.requestVideoFrameCallback( drawingLoop );
}
else if( vid.seekToNextFrame ) {
const requestNextFrame = (callback) => {
vid.addEventListener( "seeked", () => callback( vid.currentTime ), { once: true } );
vid.seekToNextFrame();
};
await vid.play();
await vid.pause();
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
ctx.filter = "invert(1)";
const drawingLoop = (timestamp) => {
log.textContent = "timestamp: " + timestamp;
ctx.drawImage( vid, 0, 0 );
requestNextFrame( drawingLoop );
};
requestNextFrame( drawingLoop );
}
else {
console.error("Your browser doesn't support any of these methods, we should fallback to timeupdate");
}
})();
video, canvas {
width: 260px;
}
<pre></pre>
<video src="https://upload.wikimedia.org/wikipedia/commons/2/22/Volcano_Lava_Sample.webm" muted controls></video>
<canvas></canvas>
Note that the encoded frames and the displayed ones are not necessarily the same thing anyway and that browser may not respect the encoded frame rate at all. So based on what you are willing to do, maybe a simple requestAnimationFrame loop, which would fire at every update of the monitor might be better.
I want to show video controls on a video element in my HTML page but I don't want the user to use the control bar to control the video. I just want the user to see the control bar so that user can just see the progress of the video and cannot skip some of its part.
You can control html5 videos playing behavior using JavaScript to trap the seeking event and interrupting the usual behavior.
See example code to control seekbar, so that user can't seek through the video:
var video = document.getElementById('video');
var supposedCurrentTime = 0;
video.addEventListener('timeupdate', function() {
if (!video.seeking) {
supposedCurrentTime = video.currentTime;
}
});
// prevent user from seeking
video.addEventListener('seeking', function() {
// guard agains infinite recursion:
// user seeks, seeking is fired, currentTime is modified, seeking is fired, current time is modified, ....
var delta = video.currentTime - supposedCurrentTime;
if (Math.abs(delta) > 0.01) {
console.log("Seeking is disabled");
video.currentTime = supposedCurrentTime;
}
});
// delete the following event handler if rewind is not required
video.addEventListener('ended', function() {
// reset state in order to allow for rewind
supposedCurrentTime = 0;
});
To react to specific space handlers I typically do this -
var fooHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
fooHandler.setInputAction(function(movement){
// do stuff
}, Cesium.ScreenSpaceEventType.WHEEL);
This function would be limited to WHEEL inputs. I have a couple of things that I need to do every time the camera changes position or height. I tried creating an event handler for the camera in a fashion similar to the above, and then calling camera.positionCartographic within that function, but to no avail.
Is there an event in Cesium that captures any movement?
You don't want to use ScreenSpaceEventHandler to do this. Instead, you subscribe to the preRender event and compare the camera position from last frame. Here's some sample code for you:
var lastTime = Cesium.getTimestamp();
var lastPosition = viewer.scene.camera.position.clone();
function preRender(scene) {
var time = Cesium.getTimestamp();
var position = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(lastPosition, position, Cesium.Math.EPSILON4)) {
document.getElementById('viewChanged').style.display = 'block';
lastTime = time;
} else if (time - lastTime > 250) {
//hide the 'view changed' message after 250 ms of inactivity
lastTime = time;
document.getElementById('viewChanged').style.display = 'none';
}
lastPosition = position.clone();
}
viewer.scene.preRender.addEventListener(preRender);
We plan on adding a viewChanged event to Cesium some time soon, perhaps with 1.8, but this code will continue to work after that and you'll be able to switch to the event at your leisure.
If you want a live demo of the above code, see this port of the view changed Google Earth demo we did in Cesium: http://analyticalgraphicsinc.github.io/cesium-google-earth-examples/examples/viewchangeEvent.html
Here's what I ended up doing:
_preRender = function (scene) {
var currentPosition = scene.camera.position;
if (!Cesium.Cartesian3.equalsEpsilon(_lastPosition, currentPosition, Cesium.Math.EPSILON4)) {
_lastPosition = currentPosition.clone();
if (typeof _positionChangeTimeout !== 'undefined' && _positionChangeTimeout !== null)
{
clearTimeout(_positionChangeTimeout);
}
var currentPositionCartographic = scene.camera.positionCartographic;
_positionChangeTimeout = setTimeout(function() {
if (typeof _positionChangeListener === 'function' && _positionChangeListener !== null)
{
_positionChangeListener({
lat: Cesium.Math.toDegrees(currentPositionCartographic.latitude),
long: Cesium.Math.toDegrees(currentPositionCartographic.longitude),
zoomLevel: _calcZoomForAltitude(currentPositionCartographic.height, currentPositionCartographic.latitude)
});
}
}, 250);
}
}
I brought this up in my last post but since it was off topic from the original question I'm posting it separately. I'm having trouble with getting my transmitted audio to play back through Web Audio the same way it would sound in a media player. I have tried 2 different transmission protocols, binaryjs and socketio, and neither make a difference when trying to play through Web Audio. To rule out the transportation of the audio data being the issue I created an example that sends the data back to the server after it's received from the client and dumps the return to stdout. Piping that into VLC results in a listening experience that you would expect to hear.
To hear the results when playing through vlc, which sounds the way it should, run the example at https://github.com/grkblood13/web-audio-stream/tree/master/vlc using the following command:
$ node webaudio_vlc_svr.js | vlc -
For whatever reason though when I try to play this same audio data through Web Audio it fails miserably. The results are random noises with large gaps of silence in between.
What is wrong with the following code that is making the playback sound so bad?
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var delayTime = 0;
var init = 0;
var audioStack = [];
client.on('stream', function(stream, meta){
stream.on('data', function(data) {
context.decodeAudioData(data, function(buffer) {
audioStack.push(buffer);
if (audioStack.length > 10 && init == 0) { init++; playBuffer(); }
}, function(err) {
console.log("err(decodeAudioData): "+err);
});
});
});
function playBuffer() {
var buffer = audioStack.shift();
setTimeout( function() {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(context.currentTime);
delayTime=source.buffer.duration*1000; // Make the next buffer wait the length of the last buffer before being played
playBuffer();
}, delayTime);
}
Full source: https://github.com/grkblood13/web-audio-stream/tree/master/binaryjs
You really can't just call source.start(audioContext.currentTime) like that.
setTimeout() has a long and imprecise latency - other main-thread stuff can be going on, so your setTimeout() calls can be delayed by milliseconds, even tens of milliseconds (by garbage collection, JS execution, layout...) Your code is trying to immediately play audio - which needs to be started within about 0.02ms accuracy to not glitch - on a timer that has tens of milliseconds of imprecision.
The whole point of the web audio system is that the audio scheduler works in a separate high-priority thread, and you can pre-schedule audio (starts, stops, and audioparam changes) at very high accuracy. You should rewrite your system to:
1) track when the first block was scheduled in audiocontext time - and DON'T schedule the first block immediately, give some latency so your network can hopefully keep up.
2) schedule each successive block received in the future based on its "next block" timing.
e.g. (note I haven't tested this code, this is off the top of my head):
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();
var delayTime = 0;
var init = 0;
var audioStack = [];
var nextTime = 0;
client.on('stream', function(stream, meta){
stream.on('data', function(data) {
context.decodeAudioData(data, function(buffer) {
audioStack.push(buffer);
if ((init!=0) || (audioStack.length > 10)) { // make sure we put at least 10 chunks in the buffer before starting
init++;
scheduleBuffers();
}
}, function(err) {
console.log("err(decodeAudioData): "+err);
});
});
});
function scheduleBuffers() {
while ( audioStack.length) {
var buffer = audioStack.shift();
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
if (nextTime == 0)
nextTime = context.currentTime + 0.05; /// add 50ms latency to work well across systems - tune this if you like
source.start(nextTime);
nextTime+=source.buffer.duration; // Make the next buffer wait the length of the last buffer before being played
};
}
I know this is not advisable. But still need this feature to be implemented. Tried everything from onseeking,onseeked to media controller. Nothing worked. Are there any external libraries to disable seeking. would be helpful if some pointers on how to go about using custom controls.
The question is quite old but still relevant so here is my solution:
var video = document.getElementById('video');
var supposedCurrentTime = 0;
video.addEventListener('timeupdate', function() {
if (!video.seeking) {
supposedCurrentTime = video.currentTime;
}
});
// prevent user from seeking
video.addEventListener('seeking', function() {
// guard agains infinite recursion:
// user seeks, seeking is fired, currentTime is modified, seeking is fired, current time is modified, ....
var delta = video.currentTime - supposedCurrentTime;
if (Math.abs(delta) > 0.01) {
console.log("Seeking is disabled");
video.currentTime = supposedCurrentTime;
}
});
// delete the following event handler if rewind is not required
video.addEventListener('ended', function() {
// reset state in order to allow for rewind
supposedCurrentTime = 0;
});
JsFiddle
It is player agnostic, works even when the default controls are shown and cannot be circumvented even by typing code in the console.
Extending the answer from #svetlin-mladenov, you can do the following to prevent the user from seeking any part of the video which has not been watched yet. This will also allow the user to rewind and the seek out any part of the video which had already watched previously.
var timeTracking = {
watchedTime: 0,
currentTime: 0
};
var lastUpdated = 'currentTime';
video.addEventListener('timeupdate', function () {
if (!video.seeking) {
if (video.currentTime > timeTracking.watchedTime) {
timeTracking.watchedTime = video.currentTime;
lastUpdated = 'watchedTime';
}
//tracking time updated after user rewinds
else {
timeTracking.currentTime = video.currentTime;
lastUpdated = 'currentTime';
}
}
});
// prevent user from seeking
video.addEventListener('seeking', function () {
// guard against infinite recursion:
// user seeks, seeking is fired, currentTime is modified, seeking is fired, current time is modified, ....
var delta = video.currentTime - timeTracking.watchedTime;
if (delta > 0) {
video.pause();
//play back from where the user started seeking after rewind or without rewind
video.currentTime = timeTracking[lastUpdated];
video.play();
}
});
You could use an HTML5 video player like video.js and use CSS to hide the seek bar.
Or you could build your own controls for HTML5 video.
Also, the event you're looking for is 'seeking'. As in (with new jquery event binding):
$(myVideoElement).on('seeking', function(){
// do something to stop seeking
})
<!DOCTYPE html>
<html>
<body>
<video controls onseeking="myFunction(this.currentTime)">
<source src="mov_bbb.mp4" type="video/mp4">
<source src="mov_bbb.ogg" type="video/ogg">
Your browser does not support HTML5 video.
</video>
<p>Video courtesy of Big Buck Bunny.</p>
<script>
var currentpos = 0;
function myFunction(time) {
if(time > currentpos) {
video.currentTime = currentpos;
}
}
var video = document.getElementsByTagName('video')[0];
function getpos(){
currentpos = video.currentTime;
}
onesecond = setInterval('getpos()', 1000);
</script>
</body>
</html>
Did the trick for me :)
var supposedCurrentTime = 0;
$("video").on("timeupdate", function() {
if (!this.seeking) {
supposedCurrentTime = this.currentTime;
}
});
// prevent user from seeking
$("video").on('seeking', function() {
// guard agains infinite recursion:
// user seeks, seeking is fired, currentTime is modified, seeking is fired, current time is modified, ....
var delta = this.currentTime - supposedCurrentTime;
if (Math.abs(delta) > 0.01) {
//console.log("Seeking is disabled");
this.currentTime = supposedCurrentTime;
}
});
$("video").on("ended", function() {
// reset state in order to allow for rewind
supposedCurrentTime = 0;
});
To hide all the video controls use this -
document.getElementById("myVideo").controls = false;
Maybe it can help someone, I use this way to remove all the progress control callbacks.
player.controlBar.progressControl.off();
player.controlBar.progressControl.seekBar.off();
Assuming this is for a standard HTML5 video player and with no 3rd party JS library interfering:
This should stop them dead in their tracks, albeit over a decade late for your needs but still useful to others...
V.onseeking=function(){event.preventDefault(); alert('Please don\'t skip the tute!');};
NB: "V" being the ID of your player.
With video js as my video player instead of the vanilla HTML5 version:
.vjs-default-skin .vjs-progress-holder {
height: 100%;
pointer-events: none;
}