admin管理员组

文章数量:1399815

I have a React ponent that plays/pauses audio when you click on a button. It works great and I render about 5 of these on a page at once. However, if you click play on one, and then click play on another, both audio's are playing, which isn't great. Here's my code for the ponent:

import React from 'react';
import playIcon from './images/play.png';
import pauseIcon from './images/pause.png';
class Music extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 'play': false };
        this.url = props.src;
        this.audio = new Audio(this.url);
        this.audio.preload = 'none';
        this.togglePlay = this.togglePlay.bind(this);
    }

    togglePlay() {
        this.setState({'play': !this.state.play}, () => {
            this.state.play ? this.audio.play() : this.audio.pause();
        });
    }

    ponentWillUnmount () {
        this.audio.pause();
    }

    render() {
        return (
            <div style={this.props.style} className={this.props.className}>
                {this.state.play
                    ? <button className="audio-button" aria-label="Pause" onClick={this.togglePlay}><img src={pauseIcon} width="34" height="34" alt="Pause"></img></button>
                    : <button className="audio-button" aria-label="Play" onClick={this.togglePlay}><img src={playIcon} width="34" height="34" alt="Play"></img></button>}
            </div>
        );
    }
}

export default Music;

I've done some looking around and a potential solution is to use redux or some other state management library. I'd like to avoid this as I otherwise have no need for that in this site. I've looked into event listeners (namely the solution proposed here) but when I do a document.getElementsByTagName('audio') I get back an empty HTMLCollection. This solution is closer, but I can't bridge the gap between it's implementation in jQuery to the one I'm using in React.

Is there a way to identify the playing audio and pause it from a React Component before playing new audio? Any and all suggestions are appreciated.

I have a React ponent that plays/pauses audio when you click on a button. It works great and I render about 5 of these on a page at once. However, if you click play on one, and then click play on another, both audio's are playing, which isn't great. Here's my code for the ponent:

import React from 'react';
import playIcon from './images/play.png';
import pauseIcon from './images/pause.png';
class Music extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 'play': false };
        this.url = props.src;
        this.audio = new Audio(this.url);
        this.audio.preload = 'none';
        this.togglePlay = this.togglePlay.bind(this);
    }

    togglePlay() {
        this.setState({'play': !this.state.play}, () => {
            this.state.play ? this.audio.play() : this.audio.pause();
        });
    }

    ponentWillUnmount () {
        this.audio.pause();
    }

    render() {
        return (
            <div style={this.props.style} className={this.props.className}>
                {this.state.play
                    ? <button className="audio-button" aria-label="Pause" onClick={this.togglePlay}><img src={pauseIcon} width="34" height="34" alt="Pause"></img></button>
                    : <button className="audio-button" aria-label="Play" onClick={this.togglePlay}><img src={playIcon} width="34" height="34" alt="Play"></img></button>}
            </div>
        );
    }
}

export default Music;

I've done some looking around and a potential solution is to use redux or some other state management library. I'd like to avoid this as I otherwise have no need for that in this site. I've looked into event listeners (namely the solution proposed here) but when I do a document.getElementsByTagName('audio') I get back an empty HTMLCollection. This solution is closer, but I can't bridge the gap between it's implementation in jQuery to the one I'm using in React.

Is there a way to identify the playing audio and pause it from a React Component before playing new audio? Any and all suggestions are appreciated.

Share Improve this question edited Jun 1, 2018 at 16:41 quicklikerabbit asked May 30, 2018 at 1:00 quicklikerabbitquicklikerabbit 3,5566 gold badges29 silver badges41 bronze badges 1
  • 1 Why isn't the audio a ponent? – Maximilian Burszley Commented May 30, 2018 at 1:12
Add a ment  | 

3 Answers 3

Reset to default 2
import React from 'react';

import audio1 from './audio1.mp3';
import audio2 from './audio2.mp3';

class AudioList extends React.Component {
    constructor (props) {
        super(props);

        this.audios = props.list.map(audio => new Audio(audio));
    }

    getCurrentAudio () {
        return this.audios.find(audio => false === audio.paused);
    }

    toggle (nextAudio) {
        const currentAudio = this.getCurrentAudio();

        if (currentAudio && currentAudio !== nextAudio) {
            currentAudio.pause();
        }

        nextAudio.paused ? nextAudio.play() : nextAudio.pause();
    }

    render () {
        return (
            <div>
                { this.audios.map((audio, index) =>
                    <button onClick={() => this.toggle(audio) }>
                        PLAY AUDIO { index }
                    </button>
                ) }
            </div>
        )
    }
}


export default () => <AudioList list={[ audio1, audio2 ]} />;

PS: new Audio(url) returns a HTMLAudioElement.

Got some big hints from Xuezheng Ma and TheIncorrigible1. Basically moved audio into it's own module. play and pause methods are exported from that, and currentlyPlaying is tracked in that module.

I adjusted my Music ponent to look like this

import React from 'react';
import playIcon from './images/play.png';
import pauseIcon from './images/pause.png';
import globalAudio from './globalAudio';

class Music extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 'play': false };
        this.name = props.src;
        this.togglePlay = this.togglePlay.bind(this);
    }

    togglePlay() {
        this.setState({'play': !this.state.play}, () => {
            this.state.play ? globalAudio.play(this.name) : globalAudio.pause(this.name);
        });
    }

    ponentWillUnmount () {
        globalAudio.pause(this.name);
    }

    render() {
        return (
            <div style={this.props.style} className={this.props.className}>
                {this.state.play
                    ? <button className="audio-button" aria-label="Pause" onClick={this.togglePlay}><img src={pauseIcon} width="34" height="34" alt="Pause"></img></button>
                    : <button className="audio-button" aria-label="Play" onClick={this.togglePlay}><img src={playIcon} width="34" height="34" alt="Play"></img></button>}
            </div>
        );
    }
}

export default Music;

And my new module globalAudio:

import audio1 from './audio1.mp3';
import audio2 from './audio2.mp3';
import audio3 from './audio3.mp3';
import audio4 from './audio4.mp3';
import audio5 from './audio5.mp3';

const audios = {
    'audio1': new Audio(audio1),
    'audio2': new Audio(audio2),
    'audio3': new Audio(audio3),
    'audio4': new Audio(audio4),
    'audio5': new Audio(audio5)
};

let currentlyPlaying = null;

const play = (name) => {
    if (currentlyPlaying) {
        audios[currentlyPlaying].pause();
    }
    audios[name].play();
    currentlyPlaying = name;
};

const pause = (name) => {
    currentlyPlaying = null;
    audios[name].pause();
};

export default {
    pause,
    play
};

Thanks for all of the suggestions, they were all very helpful.

Create a module to handle audio. Expose methods like play, pause, resume, etc, to Music, and keep track of different audios from all five Music instance you have. Whenever a new audio is played, paused the old one.

本文标签: javascriptManage Multiple Audio Sources in ReactStack Overflow