Get current time of HTML5 video before seek [duplicate] - javascript

I want to keep track of the seeks performed in an HTML5 video. For this, I need to know the seek-from and seek-to positions. While getting the second is trivial (one only has to listen to the seeked event), I am unable to figure how to get the first.
In the past, I've used custom controls, so I could save currentTime just before manually changing it to perform the seek, when listening to the mousedown event of the progress bar or whatever I had rendered.
Now, I wanted to do it with the standard controls, but I am unable to capture this last played position.
I even tried to listen the mousedown event on the video element, but it only fires when selecting outside the control area...
let video = document.getElementsByTagName('video')[0];
let list = document.getElementsByTagName('ul')[0];
function log(name, time) {
let li = document.createElement('li');
li.textContent = name + ': ' + time;
list.appendChild(li);
}
video.addEventListener('seeked', e => {
log('seeked', e.target.currentTime);
});
video.addEventListener('mousedown', e => {
log('mousedown', e.target.currentTime);
});
<video width="300" src="http://distribution.bbb3d.renderfarming.net/video/mp4/bbb_sunflower_1080p_30fps_normal.mp4" controls></video>
<ul>
</ul>
Thank you!

You could use thetimeupdate event to keep track of current time, then when a seeked event occurs you know the last known current time.
Only problem here is that at least Chrome triggers an timeupdate just before triggering seeked what would break this approach. But then we can use seeking event to know when it should stop keeping track of current time.
The only downside of this is that if user do multiples seeks to points where video has not loaded yet you'll just get the first seek start position.
let currentVideoTime = 0;
let saveCurrentTime = true;
video.addEventListener('seeked', e => {
log('seeked (started at ' + currentVideoTime + ')', e.target.currentTime);
saveCurrentTime = true;
});
video.addEventListener('seeking', e => {
log('seeking', e.target.currentTime);
saveCurrentTime = false;
});
video.addEventListener('timeupdate', e => {
log('timeupdate', e.target.currentTime);
if(saveCurrentTime)
currentVideoTime = e.target.currentTime;
});

I have been facing this problem myself, and I was able to make it work using the following trick:
var timing = 0;
video.addEventListener('timeupdate', function () {
var previousTime = timing;
var currentTime = Math.round(this.currentTime);
if (currentTime > previousTime + 1 || currentTime < previousTime - 1) {
console.log('Video ' + this.id + ' was skipped from ' + previousTime + ' to ' + currentTime + ' sec.');
}
timing = currentTime;
});
Using the timeupdate event, I check for a difference of more than 1 second between the previousTime and the currentTime". Tested on Chrome, FF and Safari with success.

My problem is that seeking even, when it's fired, video_element.currentTime is already the updated "seek to" time.
The way I do it is basically by tracking only timeupdate (except that timeupdate doesn't fire often enough [sometimes 20s? sometimes 1ms? maybe depends on load?] so I just set a timer for 100 fps and poll the currentTime. If it has changed "significantly" since last time, I assume a seek. FWIW.
It does seem that timeupdate "sometimes" fires before the seeking event does (bummer). Might make a difference whether it's forward vs. backward seek or some odd.

Take a look to video played property.
To get the seek-from position i'm using next code
const vid = document.getElementById("video1");
vid.onseeking = function() {
let seekStartTime = 0;
if (vid.played.length >= 1) {
seekStartTime = vid.played.end(vid.played.length - 1);
}
document.getElementById('videoSeekedFrom').innerHTML = "Video seeked from - " + seekStartTime + " seconds";
};
vid.onseeked = function() {
let seekEndTime = vid.currentTime;
document.getElementById('videoSeekedTo').innerHTML = "Video seeked to - " + seekEndTime + " seconds";
};

Related

How to get duration from audio tag in react? [duplicate]

I have a html5 <audio> tag in page, but how can I know its duration time?
<audio controls="">
<source src="p4.2.mp3">
</audio>
2020 solution:
You will get undefined or NaN (not a number) when the audio metadata isn't loaded yet. Therefore some people suggested to use onloadedmetadata to make sure the metadata of the audio file is fetched first. Also, what most people didn't mention is that you have to target the first index of the audio DOM element with [0] like this:
-- Vanilla Javascript:
var audio = document.getElementById('audio-1');
audio.onloadedmetadata = function() {
alert(audio.duration);
};
If this won't work try this, however not so reliable and dependent on users connection:
setTimeout(function () {
var audio = document.getElementById('audio-1');
console.log("audio", audio.duration);
}, 100);
-- JQuery:
$(document).ready(function() {
var audio = $("#audio-1")[0];
$("#audio-1").on("loadedmetadata", function() {
alert(audio.duration);
});
});
var au = document.createElement('audio');
au.addEventListener('loadedmetadata',function(){
au.setAttribute('data-time',au.duration);
},false);
In a comment above, it was mentioned that the solution is to bind an event handle to the event loadedmetadata. This is how I did that -
audio.onloadedmetadata = function() {
alert(audio.duration);
};
I was struggling with loading the duration in a React component so following #AlexioVay's solution, here is an answer if you're using React:
This assumes you are using a ref for your audio component class which you will need to target the audio elements for your play/pause handler(s).
<audio /> element:
<audio ref={audio => { this.audio = audio }} src={this.props.src} preload="auto" />
Then in your componentDidMount():
componentDidMount() {
const audio = this.audio
audio.onloadedmetadata = () => {
console.log(audio.duration)
this.setState({
duration: this.formatTime(audio.duration.toFixed(0))
})
}
}
And finally the formatTime() function:
formatTime(seconds) {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = seconds % 60
return [h, m > 9 ? m : h ? '0' + m : m || '0', s > 9 ? s : '0' + s]
.filter(a => a)
.join(':')
}
With this, the duration in h:mm:ss format will display as soon as the audio src data is loaded. Sweetness.
I used "canplaythrough" event to get the track duration. I have a case where I have two players, and I want to stop the second player 2 seconds before the first one is complete.
$('#' + _currentPlayerID).on("canplaythrough", function (e) {
var seconds = e.currentTarget.duration;
var trackmills = seconds * 1000;
var subTimeout = trackmills - 2000; //2 seconds before end
//console.log('seconds ' + seconds);
//console.log('trackmills ' + trackmills);
//console.log('subTimeout ' + subTimeout);
//Stop playing before the end of thet track
//clear this event in the Pause Event
_player2TimeoutEvent = setTimeout(function () { pausePlayer2(); }, subTimeout);
});
Simply use audioElement.duration
To obtain the end of reading it is necessary that 'loop = false' with the event 'onended'. If loop = true, onended does not work;)
To make a playlist, you have to set loop = false to use the 'onended' event in order to play the next song.
for the duration if your script is done correctly you can recover the duration anywhere. If the value of 'duration' is NaN the file is not found by the browser. If the value is 'Infinity' 'INF' it is a streaming, in this case, adds 1 mn compared to the reading time 'currentime'.
For * I.E it's crap. Currenttime may be greater than duration, in which case you do:
var duration = (this.currentime> this.duration)? this.currenttime: this.duration;
That's (O_ °)
it's better to use the event like this ...
ObjectAudio.onprogress =function(){
if(this.buffered.length){
var itimeend = (this.buffered.length>1)? this.buffered.end(this.buffered.length-1):this.buffered.end(0);
....
Your code here.......
}
}

Any way to get a currentTime value prior to the seek on HTMLMediaElement?

Let's say our app is using the default video player on Safari.
When a user is playing a video and then attempts to move to a different position of the video using the seek bar, it seems like pause event is fired first, and then we'll get seeking and seeked events fired.
I am wondering if we can get the currentTime value prior to the seek. For instance, assuming that a user jumps from t = 7 to t = 42 using the seek bar, I want to get 7 as the currentTime value somehow.
I expected that we could get this value by accessing currentTime property inside the pause event handler that is invoked right after the seek like the following:
const video = document.querySelector('#myvideo');
video.addEventListener('pause', () => {
// I expected that the `video.currentTime` here has the "previous" position,
// but it already points to the new position
console.log(video.currentTime);
});
but unfortunately the currentValue was already updated to the new value at that point.
Is there any good way to achieve it?
(EDIT)
Caching currentTime manually doesn't help, because apparently a timeupdate event fires before a pause event. More specifically, taking the following code as an example, when a user attempts to jump to another position, cache and currentTime printed within the pause handler seem always identical.
<!DOCTYPE html>
<html>
<body>
<video
id="myvideo"
width="640"
height="360"
controls
src="video.mp4"
></video>
</body>
<script>
const video = document.querySelector("#myvideo");
let cache = 0;
video.addEventListener("timeupdate", () => {
cache = video.currentTime;
});
video.addEventListener("pause", () => {
console.log({ cache, currentTime: video.currentTime });
});
</script>
</html>
I think #Kaiido means this when saying "Cache two values".
Code is untested (but looks better than being kept in comments section)
<script>
const video = document.querySelector("#myvideo");
let cache = 0;
let cache_prev = 0;
video.addEventListener("timeupdate", () => {
cache_prev = cache; //# save last known value
cache = video.currentTime; //# before updating to new currentTime
});
video.addEventListener("pause", () => {
console.log("cache_prev : " + cache_prev );
console.log("cache : " + cache );
console.log("currentTime : " + video.currentTime );
});
</script>

How to precisely time-align mouse events and audio recording events in javascript? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
How can we time-align mouse events and recorded audio data the most accurately possible, in terms of time precision?
Or, more technically: how can we estimate the time offset (relative to performance.timing.navigationStart) of the beginning of the AudioContext/ScriptProcessor audio recording?
My webapp collects mouse events and, at the same time, records audio data from the microphone (using AudioContext and ScriptProcessor).
As a dummy test for time-alignment, I click the mouse close to the microphone so that I produce a sound while pressing the mouse button (or tapping the touchscreen): I can log the event.timeStamp property (which is referred to performance.timing.navigationStart see event.timeStamp in Chrome > 49) of both the mouse onmousedown event and the audio data processing event onaudioprocess. The time of the beginning of the audio recording is estimated the first time the onaudioprocess event is fired, on the basis of the timestamp of the event. Since Chrome supports a baseLatency (see AudioContext.baseLatency), I subtract it to the timestamp (or should I add it? I'm not sure about that). The code below shows the estimation of _startRecTime.
I am currently testing it on Chrome 69 (on a Windows PC hexacore and on an ASUS quadcore tablet with Android).
Thanks to #Kaiido's suggestion to use the onmousedown event rather than onclick, now I reach a delay/anticipation of +/- 0.03 seconds, while my goal is to achieve an error of +/- 0.01 seconds maximum.
Is there a better way to estimate _startRecTime?
Here is a minimal code to monitor the mouse click events and audio events timing. Note the https protocol should be used to make the audio recording working properly:
var myAudioPeakThreshold = 0.001;
var myInChannels = 2;
var myOutChannels = 2;
var myBitsPerSample = 16;
var mySampleRate = 48000;
var myBufferSize = 16384;
var myLatency = 0.01;
var _samplesCount = 0;
var _startRecTime = 0;
function debug(txt) {
document.getElementById("debug").innerHTML += txt + "\r\n";
}
function onMouse(e) {
var tClick = e.timeStamp/1000;
debug("onMouse: " + tClick.toFixed(6));
}
function myInit() {
// thanks to Kaiido for pointing out that in this
// context "onmousedown" is more effective than "onclick"
document.getElementById("clickMe").onmousedown = onMouse;
debug("INFO: initialising navigator.mediaDevices.getUserMedia");
navigator.mediaDevices.getUserMedia({
audio: {
channelCount: myInChannels,
latency: myLatency,
sampleRate: mySampleRate,
sampleSize: myBitsPerSample
},
video: false
})
.then(
function(stream) {
debug("INFO: navigator.mediaDevices.getUserMedia initialised");
var audioContext = new AudioContext;
var audioSource = audioContext.createMediaStreamSource(stream);
debug("INFO: baseLatency is: " + (audioSource.context.baseLatency ? audioSource.context.baseLatency.toFixed(3) : "unknown") + "s");
debug("INFO: sampleRate is: " + audioSource.context.sampleRate.toFixed(0) + "Hz");
this.node = audioSource.context.createScriptProcessor.call(
audioSource.context,
myBufferSize,
myInChannels,
myOutChannels);
// audio data processing callback
this.node.onaudioprocess = function(e) {
var samplesCount = e.inputBuffer.getChannelData(0).length;
// init timing
if(_samplesCount == 0) {
_startRecTime = e.timeStamp/1000 - samplesCount / audioSource.context.sampleRate;
if(typeof audioSource.context.baseLatency !== "undefined") {
_startRecTime -= audioSource.context.baseLatency;
}
}
// simple peak detection
var tPeak = 0, i = 0;
while(i < samplesCount) {
if(e.inputBuffer.getChannelData(0)[i] > myAudioPeakThreshold) {
tPeak = _startRecTime + (_samplesCount + i)/audioSource.context.sampleRate;
debug("onPeak : " + tPeak.toFixed(6));
break;
}
i++;
}
_samplesCount += samplesCount;
}
// connect the node between source and destination
audioSource.connect(this.node);
this.node.connect(audioSource.context.destination);
return;
})
.catch(
function(e) {
debug("ERROR: navigator.mediaDevices.getUserMedia failed");
return;
});
}
<body onload="myInit()">
<button id="clickMe" style="width: 500px; height: 500px">click me</button>
<pre id="debug"></pre>
</body>

How to Avoid continuous triggering of HTML5 DeviceOrientationEvent

I am trying to tilt an image based on HTML5 DeviceOrientation event. However, I am seeing that the event is getting continuously fired even when the device is stable i.e non rotating/non moving. In the following code snippet, the console log is printed continuously. What is the possible reason, how can I stop it.
I tried both capturing and bubbling,
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function(eventData) {
var tiltLR = eventData.gamma;
console.log("tiltLR..........",tiltLR);
}, false);
}
I havent needed to use this type of event listener before so I am not familiar with the output.
However, I believe you would need to compare the old tilt with the new tilt. If the new tilt is substantially greater or less then... execute code.
if (window.DeviceOrientationEvent) {
var originalTilt = undefined,
tolerance = 5;
window.addEventListener('deviceorientation', function(eventData) {
if (eventData.gamma > originalTilt + tolerance ||
eventData.gamma < originalTilt - tolerance){
var tiltLR = eventData.gamma;
console.log("tiltLR..........",tiltLR);
originalTilt = tiltLR;
}
}, false);
}

Custom progress bar for <audio> and <progress> HTML5 elements

I am mind boggled at working out how to create a custom seekbar for an audio player using the tag and simple Javascript.
Current Code:
<script>
function play() {
document.getElementById('player').play();
}
function pause() {
document.getElementById('player').pause();
}
</script>
<audio src="sample.mp3" id="player"></audio>
<button onClick="javascript:play()" >Play</button>
<button onClick="javascript:pause()" >Pause</button>
<progress id="seekbar"></progress>
Would it be possible to link the progress bar so that when i play a song the progress is shown?
Yes, it is possible using the timeupdate event of the audio tag. You receive this event every time the position of the playback is updated. Then, you can update your progress bar using the currentTime and duration properties of the audio element.
You can see a working example in this fiddle
If you want smooth progress bar,try somethink like that
HTML:
<div class="hp_slide">
<div class="hp_range"></div>
</div>
CSS:
.hp_slide{
width:100%;
background:white;
height:25px;
}
.hp_range{
width:0;
background:black;
height:25px;
}
JS:
var player = document.getElementById('player');
player.addEventListener("timeupdate", function() {
var currentTime = player.currentTime;
var duration = player.duration;
$('.hp_range').stop(true,true).animate({'width':(currentTime +.25)/duration*100+'%'},250,'linear');
});
Pretty rough,but works
Here's a simple vanilla example:
const url = "https://upload.wikimedia.org/wikipedia/en/a/a9/Webern_-_Sehr_langsam.ogg";
const audio = new Audio(url);
const playBtn = document.querySelector("button");
const progressEl = document.querySelector('input[type="range"]');
let mouseDownOnSlider = false;
audio.addEventListener("loadeddata", () => {
progressEl.value = 0;
});
audio.addEventListener("timeupdate", () => {
if (!mouseDownOnSlider) {
progressEl.value = audio.currentTime / audio.duration * 100;
}
});
audio.addEventListener("ended", () => {
playBtn.textContent = "▶️";
});
playBtn.addEventListener("click", () => {
audio.paused ? audio.play() : audio.pause();
playBtn.textContent = audio.paused ? "▶️" : "⏸️";
});
progressEl.addEventListener("change", () => {
const pct = progressEl.value / 100;
audio.currentTime = (audio.duration || 0) * pct;
});
progressEl.addEventListener("mousedown", () => {
mouseDownOnSlider = true;
});
progressEl.addEventListener("mouseup", () => {
mouseDownOnSlider = false;
});
button {
font-size: 1.5em;
}
<button>▶️</button>
<input type="range" value="0" min="0" max="100" step="1">
The approach is to use an input[type="range"] slider to reflect the progress and allow the user to seek through the track. When the range changes, set the audio.currentTime attribute, using the slider as a percent (you could also adjust the max attribute of the slider to match the audio.duration).
In the other direction, I update the slider's progress on timeupdate event firing.
One corner case is that if the user scrolls around with their mouse down on the slider, the timeupdate event will keep firing, causing the progress to hop around between wherever the user's cursor is hovering and the current audio progress. I use a boolean and the mousedown/mouseup events on the slider to prevent this from happening.
See also JavaScript - HTML5 Audio / custom player's seekbar and current time for an extension of this code that displays the time.
First of all, don't use the progress element, it's a shitty element (for now) and styling it is a huge pain in... well it's boring (look at a little project I made, look at it (and it's juste webkit/moz)).
Anyway, you should read the doc on MDN, it's very easy and with a lot of examples. What you are looking for is the currentTime attribute, here a little snippet :
var audio = document.querySelector('#player')
audio.currentTime = 60 // will go to the 60th second
So what you need is to use the cross-multiplication (div is the element you use as a progress bar) :
Where I clicked on div | THE TIME I WANT TO KNOW
————————————————————————————————————————
Total length of div | The total time of my video/audio (audio.seekable.end())

Categories

Resources