Controlling MP4 Playback using javascript - javascript

I am trying to clone the following site:
https://www.apple.com/mac-pro/
I am still in the prototyping phase and the big ticket item I am trying to figure out is how they are playing their MP4 file backwards when you scroll up the page. If you scroll down the page a few steps and then back up, you will see what I mean.
So far I have tried the following techniques:
Tweening currentTime property of video element
Using requestAnimationFrame and using the timestamp in the callback to update the currentTime property to the desired value
Using the requestAnimationFrame technique, I am now getting a partially usable result in every browser other than Chrome. Chrome is ok if you want to rewind maybe .5 seconds, but any more than that and it will get jumpy.
I have also made the following discoveries:
Chrome hates trying to rewind an MP4 file
As much as Chrome hates rewinding MP4 files, also make sure that you don't have an audio track on your video file. It will make it even slower.
So I feel I have a pretty good understanding of the options available to me, but the one thing that makes me think I am missing something is that the Apple website functions ok in Chrome.
I have started debugging their code which is located at:
https://images.apple.com/v/mac-pro/home/b/scripts/overview.js
And from what I can tell they seem to be using requestAnimationFrame, but I can't understand why they are getting a better result.. Does anyone have any ideas on how to achieve this effect?
BTW - I understand that videos are not really meant to be played backwards and they will never play predictably backwards. I have even had occasions on the Apple website where the rewinding can be jerky. But they still have good 2-3 second rewind transitions and the result is definitely acceptable.
Here is my relevant javascript and HTML so far..
var envyVideo, currentVideoTrigger = 0,
currentIndicator, startTime, vid, playTimestamp, playTo, playAmount, triggeredTime, rewindInterval;
$(function() {
vid = document.getElementById("envy-video");
$("#play-button").click(function() {
vid.play();
});
$("#rewind-button").click(function() {
vid.pause();
playTo = parseFloat($("#play-to-time").val());
playAmount = playTo - vid.currentTime;
triggeredTime = vid.currentTime;
requestAnimationFrame(rewindToPointInTime);
});
});
function rewindToPointInTime(timestamp) {
if (!playTimestamp) playTimestamp = timestamp;
var timeDifference = (timestamp - playTimestamp) / 1000;
vid.currentTime = triggeredTime + (playAmount * (timeDifference / Math.abs(playAmount)));
if (vid.currentTime > playTo) {
requestAnimationFrame(rewindToPointInTime);
} else {
playTimestamp = null;
playAmount = null;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Rhino Envy</title>
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="./js/envy.js"></script>
<link rel="stylesheet" href="./css/envy.css">
</head>
<body;
<div id="envy-video-container">
<video id="envy-video" src="./videos/prototype_animation.mp4"></video>
</div>
<div id="video-controls">
<p id="video-current-time"></p>
<div class="video-control"><button id="rewind-button">rewind to</button><input type="text" id="play-to-time" placeholder="forward time" value="0"></div>
<button id="play-button">play</button>
</div>
<ul id="envy-steps">
<li id="envy-step-indicator-1"></li>
<li id="envy-step-indicator-2"></li>
<li id="envy-step-indicator-3"></li>
</ul>
<section id="envy-full-range">
<div id="envy-1-door-link"></div>
<div id="envy-2-door-link"></div>
<div id="envy-3-door-link"></div>
</section>
</body>
</html>

One solid way I can think of, would be to use two videos : one in normal direction, and the other one reversed.
You could then simply switch between which video is to be played, and only update the currentTime of the hidden one in the background.
With this solution, you can even rewind audio !
To reverse a video, you can use ffmpeg's command
ffmpeg -i input.mp4 -vf reverse -af areverse reversed.mp4
Note: You may feel some gaps at the switch, which could probably be leveraged by using a single visible video element, fed from the other's element streams, but I'll leave it for an update, I'm short on time r.n.
const vids = document.querySelectorAll('video');
vids.forEach(v => {
v.addEventListener('loadedmetadata', canplay);
v.addEventListener('timeupdate', timeupdate);
});
let visible = 0;
function timeupdate(evt) {
if (this !== vids[visible]) return;
let other = (visible + 1) % 2;
vids[other].currentTime = this.duration - this.currentTime;
}
document.getElementById('switch').onclick = e => {
visible = (visible + 1) % 2;
show(vids[visible]);
}
// waith both vids have loaded a bit
let loaded = 0;
function canplay() {
if (++loaded < vids.length) return;
hide(vids[1]);
show(vids[0]);
}
function show(el) {
el.muted = false;
const p = el.play();
el.style.display = 'block';
const other = vids[(visible + 1) % 2];
// only newest chrome and FF...
if (p && p.then) {
p.then(_ => hide(other));
} else {
hide(other);
}
}
function hide(el) {
el.muted = true;
el.pause();
el.style.display = 'none';
}
document.getElementById('pause').onclick = function(e) {
if (vids[visible].paused) {
this.textContent = 'pause';
vids[visible].play();
} else {
this.textContent = 'play';
vids[visible].pause();
}
}
<button id="switch">switch playing direction</button>
<button id="pause">pause</button>
<div id="vidcontainer">
<video id="normal" src="https://dl.dropboxusercontent.com/s/m2htty4a8a9fel1/tst.mp4?dl=0" loop="true"></video>
<video id="reversed" src="https://dl.dropboxusercontent.com/s/lz85k8tftj2j8x6/tst-reversed.mp4?dl=0" loop="true"></video>
</div>

Related

HTML audio duration not working in chrome

Upong calling audioElement.duraion returns infinite in chromium based browsers but works find in firefox. The song is stored locally in the same directory.
console.log("Welcome to Spotify | clone");
let songIndex = 0;
const masterPlay = document.getElementById("masterPlay");
const progressBar = document.getElementById("progressBar");
const gif = document.getElementById("gif");
const audioElement=document.getElementById("song")
// let audioElement=new Audio('./1.mp3')
// Handeling play pause and click
masterPlay.addEventListener('click', () => {
if (audioElement.paused || audioElement.currentTime <= 0) {
audioElement.play();
masterPlay.classList.remove("fa-circle-play");
masterPlay.classList.add("fa-circle-pause");
gif.style.opacity = 1;
}
else {
audioElement.pause();
masterPlay.classList.remove("fa-circle-pause");
masterPlay.classList.add("fa-circle-play");
gif.style.opacity = 0;
}
});
//EventListeners
audioElement.addEventListener("timeupdate", () => {
progress = ((audioElement.currentTime / audioElement.duration) * 100);
progressBar.value = progress;
console.log(audioElement.currentTime)
});
Expectations:
It returns the duration of the autio track.
What I tried:
Different browsers. (Works fine on firefox based browsers.)
Different song. (I found that some songs don't work on chrome but on firefox everything works.)
Tried using HTML audio element. (The problem persists)
Tried using JS audio(). (The problem still exists)
At the end of the day I found out that it was a bug with VS code Live Preview extension from Microsoft

I can't make a new Audio(url).play() to stop by new Audio(url).pause()

What I want to achieve: To stop a previously started sound when a new is started. What I have now: The sounds plays simultanously, none of them is stopping. The probably reason: Wrong logic statement in a line if (audio.played && audio.paused){. Before you judge me for not trying hard enough - I am trying to solve this from 3 days, I am a beginner. It should take me a few minutes, even an hour. I tried in several combinations.At the end I listed several websites which I tried and I still haven't solved it. In all answers is something similar but still I can't made a browser to chose, always only one part is executed either audio.play() or audio.pause() in the log. It works but not as I want and these logical statements are like on other informations on the forum. At the end of the message you can see all similar topics I already tried several times. I kept just as clear code as possible and I want to do it this way, in vanilla javascript because I won't deal for now with anything more complicated. An audio url is taken from modified id on click, the audios are on my disk. It works, I made some mistake in line if (audio.played && audio.paused){ Any ideas except giving up and changing a hobby?
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Timeout</title>
</head>
<script>
function playAudio3(clicked_id) {
var OriginalID = (clicked_id);
var res = OriginalID.slice(5,-1);
var audioID=res+'.mp3';
var url =audioID;
var audio = new Audio(url);
if (audio.played && audio.paused){
audio.play();
console.log('audio.play was used.');
}else {
audio.currentTime = 0
audio.pause();
console.log('audio.pause was used.');
}
}
</script>
<body>
<span id="guita2x"onclick="playAudio3(this.id);"> Guitar. </span>
<span id="piano2x" onclick="playAudio3(this.id);"> Piano. </span>
<span id="basse15x" onclick="playAudio3(this.id);"> Basse. </span>
</body>
</html>
how do i stop a sound when a key is released in JavaScript
Javascript Audio Play on click
Javascript to stop playing sound when another starts
HTML5 Audio stop function
cancel an if statement if another statement comes true
Stopping audio when another audio file is clicked using jQuery while obscuring link
https://www.w3schools.com/jsref/dom_obj_audio.asp
HTML5 Audio pause not working
Never give up, and don't change a hobby. :)
Possible solution from one hobbyist, too:
files = ['guitar', 'piano', 'bass']; // name of your files in array. You can get this array from looping through your html/spans id's too, but, this is basic logic
audios = []; //empty audios object array
for (i = 0; i < files.length; i++) {
var audioID = files[i] + '.mp3';
var url = audioID;
var audio = new Audio(url);
audios.push(audio); //place all audio objects into array
}
console.log(audios);
//now - basic logic to check which audio element to play, and to stop others
function playAudio3(clicked_id) {
for (i = 0; i < audios.length; i++) {
if (audios[i].src.includes(clicked_id)) {
audios[i].play();
} else {
audios[i].currentTime = 0
audios[i].pause();
}
}
}
So, you are creating all audio objects at the page load, and then choose which one to play. Of course, this will play all audio files from the start. If you want to keep the track of the audio progress, and play it from the last pause, you will need some additions, but, this could be the start, i hope.
OK, updated, i kept some of your code, and slightly changed some things in HTML.
Your complete code now should look like this: (HTML)
<span class='aud' id="guita1x" onclick="playAudio3(this.id);"> Guitar. </span>
<span class='aud' id="piano2x" onclick="playAudio3(this.id);"> Piano. </span>
<span class='aud' id="basse3x" onclick="playAudio3(this.id);"> Basse. </span>
Js:
spans=document.querySelectorAll('.aud');
console.log(spans);
audios = []; //empty audios object array
for (i = 0; i < spans.length; i++) {
var OriginalID = spans[i].id;
var res = OriginalID.slice(5,-1);
var audioID=res+'.mp3';
var url =audioID;
var audio = new Audio(url);
audios.push(audio); //place all audio objects into array
}
console.log(audios);
//now - basic logic to check which audio element to play, and to stop others
function playAudio3(clicked_id) {
for (i = 0; i < audios.length; i++) {
clickid=clicked_id.replace(/[A-Za-z]/g,'')+'.mp3';
if (audios[i].src.includes(clickid)) {
audios[i].play();
} else {
audios[i].currentTime = 0
audios[i].pause();
}
}
}
Now, i have just added class 'aud' to your spans, for easier targeting.
Then, i have collected id's from the spans and placed it in the audios array. (Not sure why you are slicing names/ids, you can simplify things by adding just numbers: 1, 2, 3 and so on).
NOW, this MUST work, IF
you have 3 .mp3 files in the same folder as your html/js file, called: 1.mp3, 2.mp3, 3.mp3.
if you place your javascript bellow HTML, BEFORE closing 'body' tag.

Video array unable to get when last video plays the next button to play again starting at first video in array

The videos all play when called. In search, addEventListener was suggested. Alas, no help. Perhaps I'm wording it wrong, but how to get the next button to go back to the first video in the array after the last video has played. JavaScript I am still learning to figure out its logic, and it is not a clean beast. Messy life is my outlook with JS. I have removed the videos as they are adult in nature (18+++), and do not want to offend anyone. The noted out are what I have tried that have no impact or negative impact on how the current setup functions.
let vids = [".mp4", ".mp4", ".mp4"]
// videoCount = vids.length;does nothing
// const found = vids.find(element => element < 3); does nothing
//Adultvideo.addEventListener('ended', next, false); does nothing
let i = 0;
let src=vids[i];
function next(e) {
i++;
if (i > 3){
// return src; does nothing
//return i; //does nothing
//const found = vids.find(element => element === 0); does nothing
// Adultvideo.load(); does nothing
//Adultvideo.play(vids[i]); does nothing
// return AdultVideo.trigger('loadstart');
}
document.getElementById("Adultvideo").src=vids[i];
}
// document.getElementById('Adultvideo').reset(src); does nothing
//src.Adultvideo.play(i);does nothing
//playVideo(i);
<body>
<header>
<h1>Adult Male Video 18 Plus !</h1></header>
<main>
<section class="vidcontainer">
<!-- Don't put control into element unless you are an adult 18+ for the vid could offend and disturb you. Likewise, do not push play either as the video could offend and disturb you -->
<!-- had to add height to control poster -->
<video id="Adultvideo" muted width="500" height="340" poster="">
<source id="Adultvideo" muted src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="Vcontrol">
<div class="Vpanel">Control Panel</div>
<button onclick="playVideo();">▷</button>
<button onclick="pauseVideo();">‖</button>
<button onclick="stopVideo();">⬜</button>
<button onclick="rewindVideo();">
⟲</button>
<button onclick="next();"><span class="reduce">▷▷</span> 𝄀</button>
</div>
</section></main>
<!-- I wish there was better instruction on this script here as copy and paste is not very good instruction -->
<script>
const Adultvideo=document.querySelector("#Adultvideo");
function playVideo() {
Adultvideo.play();
}
function pauseVideo() {
Adultvideo.pause();
}
function stopVideo() {
Adultvideo.load();
}
function rewindVideo() {
Adultvideo.currentTime= 0;
<!-- this function does not work, and unable to locate how to correct. All the extra JS does not help. The teaching sucks for this course. It does work, but still not perfect -->
function next() {
Adultvideo.next();
}
// Adultvideo.addEventListener('ended', next, false); affects nothing
}
// Adultvideo.addEventListener('ended', next, false); //affects nothing
</script>
</body>
The main question you asked is "how [do I] get the next button to go back to the first video"...
To answer that, the answer is fairly straightforward: Use the remainder operator. This is a common programming practice for wrapping a value.
For example:
// Go to the next video (and wrap)
i = (i + 1) % vids.length;
// Load the new video selection
videoSrcEl.src = vids[i];
videoEl.load();
EDIT:
The reason this works is because i++ is equivalent to i = i + 1 and the remainder after division will always be between 0 and n - 1. That second part sounds more complicated than it is, so let's just look at i % 3 as i changes (for n = 3):
If i = 0, then i % 3 = 0
If i = 1, then i % 3 = 1
If i = 2, then i % 3 = 2
If i = 3, then i % 3 = 0
If i = 4, then i % 3 = 1
If i = 5, then i % 3 = 2
If i = 6, then i % 3 = 0
...notice the pattern?
An alternative would be to use an if statement:
// Go to the next video (and wrap)
i++;
if (i > vids.length - 1) {
i = 0;
}
// Load the new video selection
videoSrcEl.src = vids[i];
videoEl.load();
Beyond that question, though, I'd like to propose a few changes to the rest of your code:
Don't use id="Adultvideo" on both the <video> and the nested <source>. IDs are expected to be unique.
Don't use inline JS like onclick="playVideo();". This requires a global function, which is rarely what you really want. The better way is to use playButton.addEventListener('click', playVideo) in the JS somewhere (and I add an IIFE to make sure nothing is global by accident)
I made a working example below, which uses a lot of newer JS syntax, but I hope you can make sense of it. These may be just my preference but note that:
arrow functions do have some differences from regular functions but they're mostly equivalent. I use them because they're shorter
I find it easier to just use classes and avoid IDs in almost all cases. I do separate CSS classes from JS classes, though
const prevents certain changes (re-assignment), but otherwise works like let
// IIFE
;(() => {
// Create a couple reusable functions (DRY up the code below)
const el = s => document.querySelector(`.js-${s}`)
const click = (s, fn) => el(s).addEventListener('click', fn)
// Get references to DOM elements (see HTML)
const videoEl = el('video')
const videoSrcEl = el('video-src')
// List of videos (I found a few random ones from across the web)
const vids = [
'https://i.imgur.com/tTaZyQC.mp4',
'https://i.imgur.com/WHifiaH.mp4',
'http://giant.gfycat.com/FineDangerousFruitbat.mp4'
]
let i = 0 // <-- which video is currently selected
// Handle clicks
click('play', () => { videoEl.play() })
click('pause', () => { videoEl.pause() })
click('stop', () => { videoEl.load() })
click('rewind', () => { videoEl.currentTime = 0 })
click('next', () => {
// Go to the next video (and wrap)
i = (i + 1) % vids.length
// Load the new video selection
videoSrcEl.src = vids[i]
videoEl.load()
})
// Load the first video
videoSrcEl.src = vids[i]
})()
<header>
<h1>Title Here</h1>
</header>
<main>
<section class="video">
<video class="js-video" muted width="500" height="340" poster="">
<source class="js-video-src" muted src="" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="video-control">
<div class="video-panel">Control Panel</div>
<button class="js-play">▷</button>
<button class="js-pause">‖</button>
<button class="js-stop">⬜</button>
<button class="js-rewind">⟲</button>
<button class="js-next">
<span class="reduce">▷▷</span>𝄀
</button>
</div>
</section>
</main>
"How to get the next button to go back to the first video in the array after the last video..."
Positions in Array start from zero as first item, so an array with 3 items has positions (index) of:
array[0], array[1], array[2]
As you can see the last item's index is 2, which is just minus 1 from a total array length of 3 items. In other words, last item pos can be calculated from array.length - 1 (eg: 3 - 1 = 2).
Try something like this:
<script>
var i = 0;
var vids = ["first.mp4", "second.mp4", "third.mp4"]; //has positions [0, 1, 2]
var videoCount = vids.length; //is 3
var myPlayer = document.getElementById("Adultvideo");
function playVideo() { myPlayer.play(); }
function pauseVideo() { myPlayer.pause(); }
function stopVideo() { myPlayer.currentTime = 0; myPlayer.pause(); }
function rewindVideo() { myPlayer.currentTime = 0; }
function next()
{
i++; //# count range is from 0 up to 2 (since total is 3 items)
if (i == 2) { i = 0; } //reset after third count
//if (i == (vids.length - 1) ) { i = 0; } //or auto-detect total items then reset
myPlayer.src = vids[i];
myPlayer.play();
}
</script>

HTML5 audio on IOS: how can currentTime be less than initial value?

I have to play a short fragment of bigger audio. I use currentTime to set starting point in time, and timeupdate event to stop audio when required.
I noticed that on few early timeupdate events sometimes currentTime is less than its initial value, despite there is (obviously) no "rewind" actions.
Here is code example:
var start = 1;
var end = 1.3;
var audio = document.getElementById('audio');
audio.addEventListener('timeupdate', function () {
console.log(audio.currentTime);
if (audio.currentTime < start || audio.currentTime > end) {
audio.pause();
}
});
audio.currentTime = start;
audio.play();
For example, output of console log can be this:
1
0.85
0.85
1
1.02
...
Here's an example.
Tested on iPad with iOS 11.4.1. This problem appears only on very short time ranges ~0.3sec.
At first you have a wrong question because you want that your script works on iOS. And because of this your question in title is irrelevant. We have a lot of bugs on iOS and the iOS developers do not want to correct them. It is a bug. Write to support from iOS and ask there "How is it possible?".
At second you have two questions in your post because your script does not work on iOS like you it want.
I wrote for you the solution. Make sure you are not trying to call the audio before the audio element has metadata loaded (first mistake). Then you have the second mistake: instead of:
if(audio.currentTime < start || audio.currentTime > end) //your mistake
you have to write:
if(audio.currentTime >= end)
The solution (it works on iOS too)
var start = 1,
end = 1.3,
audio = document.getElementById('audio'),
button = document.getElementById('button'),
log = document.getElementById('log');
audio.addEventListener('timeupdate', function()
{
writeLog(audio.currentTime);
//your mistake: if(audio.currentTime < start || audio.currentTime > end)
if(audio.currentTime >= end)
{
audio.pause();
}
});
function writeLog(value)
{
var div = document.createElement('div');
div.innerHTML = value;
log.insertBefore(div, log.firstChild);
}
audio.addEventListener('loadedmetadata', function()
{
audio.pause();
button.removeAttribute('disabled');
});
button.addEventListener('click', function()
{
log.insertBefore(log.lastChild.cloneNode(!0), log.firstChild);
audio.currentTime = start;
audio.play();
});
#log
{
width:100%;
height:18em;
overflow-y:auto;
font:3em 'Courier New';
background:#069;
color:#7e0
}
#log div:last-child{display:none}
<!--
For this link from your example (on jsfiddle.net) is only a download is possible, but not using as source:
http://techslides.com/demos/samples/sample.m4a
-->
<audio id="audio" preload="auto">
<source src="http://techslides.com/demos/samples/sample.mp3" type="audio/mpeg"/>
<source src="http://techslides.com/demos/samples/sample.ogg" type="audio/ogg"/>
<source src="http://techslides.com/demos/samples/sample.aac" type="audio/aac"/>
</audio>
<button id="button" disabled style="width:9em;height:3em;font-size:3em">Play</button>
<br><br>
<div id="log"><div><hr></div></div>
The snippet on SO does not work because it is in sandbox.
See this working example on codepen.io.

My javascript "slideshow" isnt working

Here is a small part of a website that I'm experimenting with. Anyway I'm learning Javascript and I decided to experiment by making an extremely simple "slideshow", but whenever I run this in the browser (Mozilla Firefox 42), the browser crashes. I'm wondering if anyone could tell me whats wrong or possibly give me a better way of making this(I'm sure there is but I couldn't find one). Also I know I can make this in jQuery but I want to try this out with pure Javascript.
I have also tried this on Chrome and Opera.
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
//I will use javascript to make the "slideshow" on mainSection
function loop() {
var BACKGROUND = document.getElementById("Introduction");
var SECTION1 = document.getElementById("mainSection");
var SECTION2 = document.getElementById("section2");
var SECTION3 = document.getElementById("section3");
var i = 1;
while (i <= 3) {
i = i + 0.0001;
SECTION1.style.display="block";
SECTION2.style.display="none";
SECTION3.style.display="none";
}
while (i <= 6) {
i = i + 0.0001;
SECTION1.style.display="none";
SECTION2.style.display="block";
SECTION3.style.display="none";
}
while (i <= 9) {
i = i + 0.0001;
SECTION1.style.display="none";
SECTION2.style.display="none";
SECTION3.style.display="block";
}
while (i = 9) {
i = 1;
}
}
</script>
</head>
<body onload="loop();">
<header id="Introduction">
<section id="mainSection">
<h1>Welcome to the Ipsum Hotel.</h1>
<p>This is where thought and precision goes<br />
towards the comfort of our customers.<br /></p>
</section>
<!--This will be used to make a slideshow-->
<section id="section2">
<p><strong>Free Wifi!</strong></p>
<p>Free wifi is included in all of our guest<br />
packages from Economy to Deluxe<br /></p>
</section>
<section id="section3">
<p><strong>Free Hot Breakfast</strong></p>
<p>Included with any of our guest packages is<br />
a hearty breakfast. You can also get a personalized<br />
omelet at our omelet bar.<br /></p>
</section>
</header>
</body>
</html>
Try using setInterval. As mentioned in the comments, a while loop will block all page operations and tax your CPU. setInterval is designed specifically for handling timing operations in a more resource-friendly manner.
var SECTION1 = document.getElementById("mainSection");
var SECTION2 = document.getElementById("section2");
var SECTION3 = document.getElementById("section3");
var sections = [SECTION1, SECTION2, SECTION3];
var activeSection = 0;
setInterval(function() {
// If we are past the end of list, reset to first element
if (activeSection >= sections.length) {
activeSection = 0;
}
// Mark only the activeSection as visible
sections.forEach(function(section, idx) {
section.style.display = activeSection === idx ? 'block' : 'none';
});
activeSection += 1;
}, 3000);
This will loop over each section every three seconds (at the least, see https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval for more info about how timers work in the browser). Once the last section is reached, it resets to the first section.

Categories