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!
Related
I want to create an app that shows different songs i've chosen, with react.js
The problem is that it doesn't work with local files. Path is ok (just checked in the DOM)
I've tried with relative path and absolute path but it is still the same.
I've tried a lot of things, such as importing react-sound, react-audio-player...
I've tried to directly import the mp3 file at the beginning of the file with "import song from "./songs/song.mp3" and it works, but it is useless as you have to add it manually if you want to add/delete songs.
When I press the play button, it always fails the promise and returns me the error :
DOMException: Failed to load because no supported source was found.
import React from "react";
const SongCard = (props) => {
const playAudio = () => {
const audio = new Audio(props.song.path)
const audioPromise = audio.play()
if (audioPromise !== undefined) {
audioPromise
.then(() => {
// autoplay started
console.log("works")
})
.catch((err) => {
// catch dom exception
console.info(err)
})
}
}
return (
<div className="songCard">
<div className="coverContainer">
<img src=""/>
</div>
<div className="infoContainer">
<div className="playPauseButton" onClick={playAudio}>►</div>
<div className="songTitle">{props.song.nom}</div>
</div>
</div>
);
};
export default SongCard;
Does anyone have an idea ?
Due to security issues, you won't be able to access local files programatically from JavaScript running in browser.
The only way you can get a hold of local files is by:
User selecting the file via a file <input>
User drag and dropping the files into your application
This way the user is explicitly giving you access to those files.
You can either design your application around those interactions, or
You can start a web server locally where it has access to your audio files and stream those to your app or upload the files to a cloud service.
you can do it by using this code:
const SongCard = (props) => {
const playAudio = () => {
let path = require("./" + props.song.path).default;
const audio = new Audio(path);
const audioPromise = audio.play();
if (audioPromise !== undefined) {
audioPromise
.then(() => {
// autoplay started
console.log("works");
})
.catch((err) => {
// catch dom exception
console.info(err);
});
}
};
return (
<div className="songCard">
<div className="coverContainer">
<img src="" />
</div>
<div className="infoContainer">
<div className="playPauseButton" onClick={playAudio}>
►
</div>
<div className="songTitle">{props.song.nom}</div>
</div>
</div>
);
};
export default SongCard;
it'll work if you change the "./" in the require to the relative path of the audio's dictionary, and send only the name of the audio file in the parent's props
I hope I was help you
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();
I need to be able update button class styles after page loads.
Tried doing it in Render() and i have seen people talk about setTimeout and setInterval, but this other way with event is working part of the time
ComponentDidMount makes a axios web api call grabs data and in a map all is in render. I need to check local storage and such, and want to update button color and text, so I tried many things, but then tried window.addeventlistener ... this.handleload
Seems to work only part of the time.... its like it is happening TOO FAST, I don't want to add "hack" like timers, but i'm stuck with no idea how to do this.
I tried calling functions in the render as well. Not sure why this is so hard to do.
handleLoad() {
alert('always runs from outside loop');
// loop ONLY runs after refreshing browser several times
for (var i = 0; i < this.state.data.length; i++) {
//rarely makes it in
alert('made it');
document.getElementById("4534552").classList.remove('btn-warning');
}
}
componentDidMount() {
webApi.get('sai/getofflinemembers?userId=N634806')
.then((event) => {
//........
}
// THIS is what i call
window.addEventListener('load', this.handleLoad);
}
render() {
const contents = this.state.data.map(item => (
<button id={item.Member_ID} type="button" onClick={(e) => this.downloadUser(item.Member_ID,e)}
className="btn btn-warning">Ready for Download</button>
)
}
I just need to call a function and loop over all the DOM and change it as needed. Now that has me thinking about react creating a virtual DOM to which i don't know.
Needing to check local storage if a member is already set, then set the class of a bootstrap button to a specific color and text as well.
Thoughts?
Just an advice: If you want to manipulate the DOM, it's better to use refs instead of using document.getElementById. https://reactjs.org/docs/glossary.html#refs
But your problem can be solved by using state to store the css class:
class SomeComponent extends React.Component {
state = {
buttonCSSClass: 'btn btn-warning',
}
componentDidMount() {
webApi.get('sai/getofflinemembers?userId=N634806')
.then((event) => {})
.then(() => this.setState({buttonCSSClass: 'btn'})) // it will update the css class
}
}
render() {
const { data, buttonCSSClass } = this.state
return data.map(item => (
<button
key={item.Member_ID}
id={item.Member_ID}
type="button"
onClick={e => this.downloadUser(item.Member_ID, e)}
className={buttonCSSClass}
>
Ready for Download
</button>
))
}
}
Angular2/Typescript - Parent/Child Directive(?)
I'm new to, and very much still learning, Angular2/Typescript/javascript. As a result, I'm not entirely sure how to title my question. The basis of my app is a card game. The premise (relative to my struggle) is that the game has 2 players and each player has a hand of 5 cards. I have API calls to build/return the hand of cards.
In my app.component template, I have 2 div blocks; one for each players' hand of cards. Currently, I have it working by building two distinct arrays of cards (named p1cards and p2cards). Here is the relative code for that:
<div class="player1Cards" id="player1Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p1cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
<div class="player2Cards" id="player2Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p2cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
And here is the actual export class of the entire AppComponent:
#Injectable()
export class AppComponent implements OnInit
{
#ViewChild(ModalComponent) errorMsg: ModalComponent;
errorMessage: string;
gameBoard: GameBoard[];
name: {};
mode = 'Observable';
//we need a gameboard (maybe not)
//we need an array of players
player:Player;
players:Player[] = [];
p1cards:Card[] = [];
p2cards:Card[] = [];
droppedItems = [];
//This tells us where the card images can be found
cardBluePath = "/assets/deck/Blue/";
cardRedPath = "/assets/deck/Red/";
//The boardService will handle our API calls
boardService;
//Initialize the API service
constructor(boardService:BoardService) {
this.boardService = boardService;
}
//On load...
ngOnInit()
{
//Create the game
this.boardService.createGame()
.subscribe(
error => this.errorMessage = <any>error);
//Create the players
this.createPlayer(0);
this.createPlayer(1);
}
createPlayer(player: number)
{
var playerName;
if (player == 0) {playerName = "Player1"} else {playerName = "Player2"};
//We'll make a call to the API to build the hand of cards
this.boardService.buildHand(player)
.subscribe(
cardList =>
{
var cardData = [];
cardData = JSON.parse(cardList.toString());
var i, itemLength, card
itemLength = cardData.length;
for(i=0;i<itemLength;i++)
{
let card = new Card();
Object.assign(card,
{
"cardNum":i,
"id": cardData[i].id,
"displayName": cardData[i].displayName,
"fileName": cardData[i].fileName,
"left": cardData[i].left,
"top": cardData[i].top,
"right": cardData[i].right,
"bottom": cardData[i].bottom,
"level": cardData[i].level,
"native": cardData[i].native
});
if (player == 0) {this.p1cards.push(card)} else {this.p2cards.push(card)};
////this.cards.push(card);
}
//Now we will create the player and feed it the hand
this.player = new Player(playerName);
if (player ==0) {this.player.cardHand = this.p1cards} else {this.player.cardHand = this.p2cards};
this.players.push(this.player);
}
);
}
//When a card is dropped...
onItemDrop(e: any, slot: any)
{
e.dragData.slot = slot;
//Update the object
this.boardService.playCard(slot, e.dragData.card)
.subscribe(result => {
//If the slot is open and the card is played, physically move the item
if (result == "true" )
{
this.droppedItems.push(e.dragData);
this.removeItem(e.dragData, this.p1cards);
}
else{
window.alert("Slot already occupied.");
//this.modalWindow.show()
//this.errorMsg.showErrorMessage("Slot already occupied.");
//this.errorMsg.show();
}
});
}
//Remove the card from the hand
removeItem(item: any, list: Array<any>)
{
let index = list.map((e) => {
return e.cardNum
}).indexOf(item.cardNum);
list.splice(index, 1);
}
}
The createPlayer function is really where the question begins. Currently, it will make the API call and parse the JSON back into an array of cards. Right now, the array of cards lives locally in the AppComponent (as p1cards or p2cards).
What I want to do instead is create a player objects (component) for each player, assign their respective hand of cards, and then put those players in an array. I had that part working (pieces of the code still exist above, but not all of it), but I hit a wall in my *ngFor to display the cards. In pseudocode, I understood what I needed to do, but in practice I couldn't figure it out.
I knew that div class player1Cards needed to be something like "let player of Players where name = player1", and then I needed to iterate over the player.cardHand[] array to display each of the cards. I tried quite a few things, but nothing worked.
So then, after a few hours of Google searching, I came to the conclusion that I needed a child view for the player to handle it. I currently have the following for that:
My player.html is:
<div draggable *ngFor="let card of cardHand" [dragData]="card" class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
And my player.ts is:
import { Component, Input, OnInit } from '#angular/core';
import { Card } from './card';
#Component({
selector: 'player',
templateUrl: './player.html',
})
export class Player implements OnInit
{
public cardHand: Card[];
cardBluePath = "/assets/deck/Blue/";
constructor
(
public name: string
)
{}
ngOnInit()
{
}
}
Then in my AppComponent template, I added the block (and Imported the player.ts)
I get an error message on App.Component "inline template:69:16 caused by: No provider for String!". Of all the Google research I performed and all of the changes I tried (ViewChild, Input/Output, Reference), I could not get it to work. I don't recall exactly what I did, but at one point I was able to eliminate the error, but the card array was not getting passed to the player (I wish I had committed or stashed that code).
In my mind, I understand the task at hand, I just can't make it happen. I know I need to create the Player object and feed it the respective cardHand in order for the player html to be able to parse it. I can do that fine in AppComponent, but once I try to do it as a parent/child, I get stuck.
Can someone help get me going in the right direction?
I know I need to create the Player object and feed it the respective
cardHand in order for the player html to be able to parse it. I can do
that fine in AppComponent, but once I try to do it as a parent/child,
I get stuck.
I agree that it makes sense to create an array of player objects, each with an array of cards in their hand. Something like this:
let player1 = {name:'Player 1',hand:[]}
let player2 = {name:'Player 2',hand:[]}
this.players = [player1, player2]
player1.hand.push(this.dealCard())
...
player2.hand.push(this.dealCard())
...
You can then create a player component to show the players (and even a card component to show their hand). In your root template you'll loop through the players, creating your player component and passing in the player data, including their hands.
<player-component *ngFor="let player of players" [player]="player"></player-component>
Make sure your player component has an input to receive the player data:
export class PlayerComponent implements OnInit {
#Input() player: Player;
constructor() { }
ngOnInit() { }
}
Then in the <player-component> template loop through the player's hand and render the cards:
<p>I am {{player.name}}. My hand is:</p>
<ul>
<li *ngFor="let card of player.hand">{{card}}</li>
</ul>
Here is a plunker showing a working demo that's a simplified version of this setup:
https://plnkr.co/edit/5Hz8P7poCb9Ju5IR6MWs?p=preview
You should be able to configure it to the specific setup of your game. Good luck!
in your player component, if you want to access another component:
1. that component needs a import statement on top
2. within the #component section, you need to include it in Providers
3. also include it in the constructor
For more information, visit here: https://angular.io/guide/dependency-injection
I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.