I have a custom video player with JS, html and css. Crux of my issue here is I didn't anticipate scaling this from one video, to two videos and I'm looking to refactor this so I can play multiple videos on one page. I've tried rewriting everything into a forEach and haven't been able to crack it. Really just need someone to nudge me in the right direction here:
Fiddle
My thinking was to simply change const player = document.querySelector('.custom-video-player'); to const players = document.querySelectorAll('.custom-video-player'); and then scope something like:
players.forEach((player) => {
// declare all the consts here... and event listeners
})
However, this approach isn't really working. Ideally I wanted to be lazy and not rewrite each instance of player. At this point I'm pretty stuck...
HTML
<div class="cs__video">
<div class="custom-video-player">
<video class="player__video viewer" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
<div class="custom-video-player">
<video class="player__video viewer" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
</div>
JS
/* custom video player javascripts */
// declaring elements
const player = document.querySelector('.custom-video-player');
const video = player.querySelector('.viewer');
/* Build out functions */
function togglePlay() {
console.log('playing');
const method = video.paused ? 'play' : 'pause';
video[method]();
}
/* event listeners */
video.addEventListener('click', togglePlay);
video.addEventListener('play', updateButton);
video.addEventListener('pause', updateButton);
toggle.addEventListener('click', togglePlay);
You may find it easier to manage the multiple players if you create each one from a class that includes all the relevant setup and methods.
Once you create the class for all players it's easy to create as many as you like.
Here's an example that creates an array of two players from an array of video sources (also available as a fiddle).
class Player {
// We call `new Player()` with two arguments, the id
// and the video source
constructor(id, src) {
// We assign both id and src to the class
this.id = id;
this.src = src;
// Then we call two functions, one to generate the
// video HTML, and one to add it to the page
const html = this.generateHTML(id);
this.addHTMLToDOM(html);
}
// We use a template literal to build our HTML
// using the id and src we passed into the class earlier
generateHTML() {
return (
`<div data-player=${this.id}>Player ${this.id}</div>
<video controls width="250">
<source src="${this.src}" type="video/mp4" />
Sorry, your browser doesn't support embedded videos.
</video>`
);
}
// This method simply adds the player HTML
// to the document body
addHTMLToDOM(html) {
document.body.insertAdjacentHTML('beforeend', html);
}
// play and pause are a couple of example methods for
// player control. `return this` allows for the methods
// to be chained (see below)
play() {
console.log(`Playing video ${this.id}`);
return this;
}
pause() {
console.log(`Pausing video ${this.id}`);
return this;
}
}
// An array of video sources
const srcs = [
'http://techslides.com/demos/sample-videos/small.mp4',
'http://techslides.com/demos/sample-videos/small.mp4'
]
// `map` over the srcs array to create an array of new players
const players = srcs.map((src, i) => new Player(++i, src));
// An example to show how we can call the player instance methods
players[0].play().pause();
players[1].play().pause();
Related
I can't figure out how to use tone.js on multiple pages. My tone.js component duplicates the sound effects so that after each page change, the sound gets louder and louder.
Code: TestTone.vue
<script setup>
import * as Tone from 'tone'
let synth
let loop
onMounted(() => {
// need to initialize Tone.js in Browser, not on server
createSetup()
})
function createSetup () {
synth = new Tone.PolySynth()
synth.toDestination()
loop = new Tone.Loop(loopStep, '4n')
loop.start()
synth.context.resume() // necessary for Safari
}
function loopStep(time) {
// triggerAttackRelease: freq/note, noteDuration, time=now
synth.triggerAttackRelease(220, '8n', time)
}
function playTone () {
// need to play sound on the current page
Tone.start();
if (Tone.Transport.state !== 'started') {
Tone.Transport.start();
} else {
Tone.Transport.stop();
}
}
// prevent play noise on page transitions
onBeforeMount(() => Tone.Transport.stop())
onBeforeUnmount(() => Tone.Transport.stop())
</script>
<template>
<div>
<button #click="playTone()">play</button>
</div>
</template>
Problem
I use the TestTone.vue component on a few pages so that the createSetup() method is called each time and a new Tone.PolySynth is created. After each page transition and playing a few tones, the tone gets louder and louder.
Questions
How can I use a single Tone instance on multiple pages without causing interference?
Should I remove/clean up the new Tone instances on page transitions and create new instances?
My attempt
I added the synth.disconnect() method on the unmounted hook:
onBeforeUnmount(() => {
Tone.Transport.stop()
synth.disconnect()
})
Now the sound is not duplicated, but how can I be sure that other instances are not duplicated and memory leaks occur?
Thanks!
I want to add an html "audio off" button to a webpage containing a WebGL Unity game.
I know I can do adding a button to my webgl game, but I want to do with outside html button.
My code:
<img id="btnaudio" src="images/audio-on.png" style="height: 24px;" data-toggle="tooltip" title="Audio On/Off" onclick="switchAudio();" />
<div id="gameContainer" ></div>
function switchAudio() {
document.getElementById("gameContainer").muted = !document.getElementById("gameContainer").muted;
var img = document.getElementById('btnaudio');
if(document.getElementById("gameContainer").muted)
img.src='images/audio-on.png';
else
img.src='images/audio-off.png';
}
But it not work.. audio keep playing (but image swap so, js code is executed).
Thanks
I think one way would probably be to simply turn off the camera's AudioListener component.
You can call methods in your Unity scene from the page's JavaScript e.g. like
c# code
public class MuteController : MonoBehaviour
{
// For storing previous volumes
private readonly Dictionary<AudioListener, float> mutedListeners = new Dictionary<AudioListener, float>();
public void Mute()
{
foreach (var listener in FindObjectsOfType<AudioListener>())
{
mutedListeners.Add(listener, listener.volume);
listener.volume = 0;
}
}
public void Unmute()
{
foreach (var kvp in mutedListeners)
{
kvp.Key.volume = kvp.Value;
}
mutedListeners.Clear();
}
}
Put it on a GameObject in your scene with the name MuteController.
And then call these methods from the JavaScript code like e.g.
unityInstance.SendMessage('MuteController', 'Mute');
and
unityInstance.SendMessage('MuteController', 'Unmute');
I'm trying to experiment with a HowlerJS playlist code. I would like to make a button that checks if a specific song in the playlist is playing, and if so, hide a line of text when clicked. I'm not very knowledgeable with these things, so I've been shooting in the dark trying to get it working. Is it possible to use this playlist code and make the type of button I need? This is the playlist and the code to hide the line of text:
var player = new Player([
{
title: 'Rave Digger',
file: 'rave_digger',
howl: null
},
{
title: '80s Vibe',
file: '80s_vibe',
howl: null
},
{
title: 'Running Out',
file: 'running_out',
howl: null
}
]);
function checksound() {
if(rave_digger.playing()){
$(".note").hide();
}
}
<div class="button" onclick="checksound();">Click here</div>
<div class="note">remove this text if Rave Digger is playing</div>
This would be easy if the songs were individually defined, but I can't figure it out due to how the songs are retrieved and defined:
sound = data.howl = new Howl({
src: ['./audio/' + data.file + '.webm', './audio/' + data.file + '.mp3'],
html5: true,});}
sound.play();
Here is the full code I am using: https://jsfiddle.net/vfozrn9d/1/
Is there any way I could specify that the file "rave_digger" should be checked for ".playing()"?
Had a look at the Howler player.js code on github and it looks like the player object will expose a playlist array and an index property
so you could write a function that checks the playlist to see if a certain track is playing
function isTrackPlaying(filename) {
// assume the track is not playing
let playing = false;
// Retrieve the currently active track listing from the playlist
const track = player.playlist[player.index];
// is it the track we are looking for?
if(track.file == filename) {
// has it been initialised, and is it playing?
playing = track.howl && track.howl.playing();
}
return playing;
}
Then your checksound function would become
function checksound() {
if(isTrackPlaying('rave_digger')){
$(".note").hide();
}
}
i am making a custom video player in which there is an overlay containing the controls of the video player
my player starts to work in full length and height.
now i want to hide the overlay after 5 seconds i stop the mouse over.
now the problem is that when the below function mouse over in .ts file is called the synchronization of the timer is harmed.
so if i move my mouse continuously the overlay starts to flicker.
please provide me the solution to the problem.
following is my html code
<div class="video-container" #videoFullscreen>
<div class="video-wrapper" mouse-move>
<video class="video video-js" data-dashjs-player id="myVideo" autoplay #videoPlayer>
<source src="{{ videoSource }}" type="video/mp4" />
</video>
<!-- overlay -->
<div class="overlay" [class.hideOverlay]="hideTop">
<!-- top controls -->
.
.
<!-- lower controls -->
</div>
</div>
this is my type script code
#HostListener('document:mousemove', [ '$event' ]) //fuction to display and hide element sue to mouseover
onMouseMove($event) {
this.hideTop = false;
setTimeout(() => {
this.hideTop = true;
}, 5000);
}
this is my css code :
.overlay {
display: flex;
}
.hideOverlay {
display:none;
}
please help me to solve this problem.
Store the lastHover time and compare against it.
private lastHover = 0;
#HostListener(...)
onMouseMove($event) {
this.lastHover = new Date().getTime()
This.hideTop = true;
setTimeout( () => {
...
if(lastHover + 5000 < new Date().getTime()) {
This.hideTop = true;
}
}, 5000)
}
A neat solution would be to use rxjs to solve this like shown below:
ngOnInit(): void {
fromEvent<MouseEvent>(document, 'mousemove').pipe(tap(() => {
console.log("show it!");
this.hideTop = false
}), switchMap((event) =>
timer(5000).pipe(tap(() => {
console.log("hideit");
this.hideTop = true;
}))
)).subscribe();
}
Don't forget to unsubscribe if your component gets destroyed to prevent memory leaks!
First we make an Observable from the documents mousemove event.
Now if the event triggers we set hideTop to true.
And here comes the interesting part: we use switchMap with a timer Observable. switchMap automatically unsubscribes from the inner Observable if the outer one emits a new value. Therefore the inner Observable only emits after the user actually stopped moving the mouse for 5 seconds.
Apologies that my answer is in jQuery, but the concept is fairly basic
What we need to do is check if the timeout event has already been fired, and reset it on a mousemove event during that time. This is done by checking if the class for hiding the element is applied or not
//Timer variable
var timer;
//Detect mousemove event on parent element
$("html").on("mousemove", "#outer", function() {
//Is the element already hidden?
if ($("#inner").hasClass("hide")) {
//Show the element
$("#inner").removeClass("hide")
} else {
//Reset the timer to 5 seconds
clearTimeout(timer);
timer = setTimeout(hideBox, 5000);
}
})
function hideBox() {
$("#inner").addClass("hide")
}
https://jsfiddle.net/xcL52zf3/1/
You'll need to swap out the jQuery event handlers and element targetting with the equivalent for you TypeScript library
When I trying to use the newest version of videojs 5, the following code is no longer worked. I am trying to write a videojs plugin, but videojs 5 use ecmascript 6, which is new to me. Any helps are appreciated.
videojs.SharingButton = videojs.Button.extend({
/** #constructor */
init: function(player, options){
videojs.Button.call(this, player, options);
this.player = player;
}
});
videojs.SharingButton.prototype.createEl = function(tagName,options) {
return videojs.Component.prototype.createEl(tagName,{
className: this.buildCSSClass(),
innerHTML: '',
role: 'button',
'aria-live': 'polite', // let the screen reader user know that the text of the button may change
tabIndex: 0
});
}
videojs.SharingButton.prototype.buttonText = 'Share Video';
videojs.SharingButton.prototype.options_ = {};
videojs.SharingButton.prototype.buildCSSClass = function(){
return 'vjs-sharing-control ';
};
Hi i had the same problem, Replace this Code
videojs.SharingButton = videojs.Button.extend({
by
var SharingButton = videojs.getComponent('Button');
videojs.SharingButton = videojs.extend(SharingButton , {...});
videojs.registerComponent('SharingButton', SharingButton);
var myButton = myPlayer.addChild('SharingButton');
If you want to add a Component that is not a direct child of the player element you will have to climb the child elements and add the Component.
Like:
parentComponent = myPlayer.getChild('component1').getChild('component2')...
parentComponent.addChild('SharingButton')
Beware that the player components have to start lowercase like e.g. controlBar.
Find the component tree in this link.
A lot of changes has being made as version 5.0 is built (see this link), and unfortunately most videojs plugins didn't make an update of their codes! one of theme is Social button sharing