javascript 在React.js中播放声音

mqkwyuun  于 2023-02-18  发布在  Java
关注(0)|答案(7)|浏览(106)
import React, { Component } from 'react'
import { Button, Input, Icon,Dropdown,Card} from 'semantic-ui-react'
import { Link } from 'react-router-dom'
import $ from 'jquery'
import styles from './Home.scss'
import Modal from './Modal.jsx'
import MakeChannelModal from './MakeChannelModal.jsx'

class Music extends React.Component {
    constructor(props) {
    super(props);
    this.state = {

      play: false,
      pause: true

    };

    this.url = "http://streaming.tdiradio.com:8000/house.mp3";
    this.audio = new Audio(this.url);

  }

  play(){
    this.setState({
      play: true,
      pause: false
    });
    console.log(this.audio);
    this.audio.play();
  }
  
  pause(){
  this.setState({ play: false, pause: true });
    this.audio.pause();
  }
  
  render() {
    
  return (
    <div>
      <button onClick={this.play}>Play</button>
      <button onClick={this.pause}>Pause</button>
    </div>
    );
  }
}

export default Music

这是我在react应用程序中使用url(this. url)播放声音的代码。当我按下播放按钮时,它给我一个错误
未捕获的类型错误:无法读取未定义的属性"setState"
我不确定为什么会发生这种情况,因为我没有看到任何未定义的状态。;州已宣布。
我是新来的React,所以我可能错过了一些非常重要的东西。
救命啊!

ghg1uchk

ghg1uchk1#

ES6类属性语法

class Music extends React.Component {
  state = {
    play: false
  }
  audio = new Audio(this.props.url)

  componentDidMount() {
    audio.addEventListener('ended', () => this.setState({ play: false }));
  }
  
  componentWillUnmount() {
    audio.removeEventListener('ended', () => this.setState({ play: false }));  
  }

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

  render() {
    return (
      <div>
        <button onClick={this.togglePlay}>{this.state.play ? 'Pause' : 'Play'}</button>
      </div>
    );
  }
}

export default Music;

挂钩版本(React 16.8+):

import React, { useState, useEffect } from "react";

const useAudio = url => {
  const [audio] = useState(new Audio(url));
  const [playing, setPlaying] = useState(false);

  const toggle = () => setPlaying(!playing);

  useEffect(() => {
      playing ? audio.play() : audio.pause();
    },
    [playing]
  );

  useEffect(() => {
    audio.addEventListener('ended', () => setPlaying(false));
    return () => {
      audio.removeEventListener('ended', () => setPlaying(false));
    };
  }, []);

  return [playing, toggle];
};

const Player = ({ url }) => {
  const [playing, toggle] = useAudio(url);

  return (
    <div>
      <button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
    </div>
  );
};

export default Player;

2020年3月16日更新:多个并发玩家

回应@冷课评论:
不幸的是,如果我使用多个这样的组件,当我开始播放另一个组件时,其他组件的音乐不会停止播放-有什么建议可以简单地解决这个问题吗?
不幸的是,没有一个直接的解决方案使用我们用来实现单个Player组件的代码库,原因是您必须以某种方式将单个玩家状态提升到MultiPlayer父组件,以便toggle函数能够暂停您直接交互的玩家之外的其他玩家。
一种解决方案是修改钩子本身来同时管理多个音频源。下面是一个示例实现:

import React, { useState, useEffect } from 'react'

const useMultiAudio = urls => {
  const [sources] = useState(
    urls.map(url => {
      return {
        url,
        audio: new Audio(url),
      }
    }),
  )

  const [players, setPlayers] = useState(
    urls.map(url => {
      return {
        url,
        playing: false,
      }
    }),
  )

  const toggle = targetIndex => () => {
    const newPlayers = [...players]
    const currentIndex = players.findIndex(p => p.playing === true)
    if (currentIndex !== -1 && currentIndex !== targetIndex) {
      newPlayers[currentIndex].playing = false
      newPlayers[targetIndex].playing = true
    } else if (currentIndex !== -1) {
      newPlayers[targetIndex].playing = false
    } else {
      newPlayers[targetIndex].playing = true
    }
    setPlayers(newPlayers)
  }

  useEffect(() => {
    sources.forEach((source, i) => {
      players[i].playing ? source.audio.play() : source.audio.pause()
    })
  }, [sources, players])

  useEffect(() => {
    sources.forEach((source, i) => {
      source.audio.addEventListener('ended', () => {
        const newPlayers = [...players]
        newPlayers[i].playing = false
        setPlayers(newPlayers)
      })
    })
    return () => {
      sources.forEach((source, i) => {
        source.audio.removeEventListener('ended', () => {
          const newPlayers = [...players]
          newPlayers[i].playing = false
          setPlayers(newPlayers)
        })
      })
    }
  }, [])

  return [players, toggle]
}

const MultiPlayer = ({ urls }) => {
  const [players, toggle] = useMultiAudio(urls)

  return (
    <div>
      {players.map((player, i) => (
        <Player key={i} player={player} toggle={toggle(i)} />
      ))}
    </div>
  )
}

const Player = ({ player, toggle }) => (
  <div>
    <p>Stream URL: {player.url}</p>
    <button onClick={toggle}>{player.playing ? 'Pause' : 'Play'}</button>
  </div>
)

export default MultiPlayer

使用MultiPlayer组件的App.js示例:

import React from 'react'
import './App.css'
import MultiPlayer from './MultiPlayer'

function App() {
  return (
    <div className="App">
      <MultiPlayer
        urls={[
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3',
          'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3',
        ]}
      />
    </div>
  )
}

export default App

其思路是管理2个并行阵列:

  • 你的音频源(从你传递给父组件的urls prop 构建;urls props是一个字符串数组(您的MP3 URL)
  • 跟踪每个玩家状态的数组

toggle方法根据以下逻辑更新播放器状态数组:

  • 如果有一个播放器当前处于活动状态(即音频正在播放),并且此活动播放器不是toggle方法的目标播放器,则将该播放器的播放状态恢复为false,并将目标播放器的播放状态设置为true [您在 * 另一个 * 音频流正在播放时单击了'播放']
  • 如果当前活动的玩家是toggle方法的目标玩家,只需将目标玩家的播放状态恢复为false [您单击了'pause']
  • 如果当前没有活动的播放器,只需将目标播放器的状态设置为真[当前没有音频流正在播放时单击'播放']

注意,toggle方法被修改为接受源播放器的索引(即,单击相应按钮的子组件的索引)。
实际的音频对象控制在useEffect中发生,就像在原始钩子中一样,但是稍微复杂一些,因为我们必须在每次更新时迭代整个音频对象数组。
类似地,音频流“ended”事件的事件侦听器在第二个useEffect中处理,就像在原始钩子中一样,但更新为处理音频对象数组,而不是单个这样的对象。
最后,从父MultiPlayer组件(包含多个播放器)调用新的钩子,然后使用(a)包含播放器当前状态及其源流URL的对象和(b)使用播放器索引的toggle方法Map到各个Player
CodeSandbox demo

zlhcx6iw

zlhcx6iw2#

您也可以使用useSound钩子来完成此操作。
为此,首先安装npm软件包:

npm install use-sound

进口:

import useSound from 'use-sound'
import mySound from '../assets/sounds/yourSound.mp3' // Your sound file path here

用法示例1

一个简单的方法..

function MyButton(){
  const [playSound] = useSound(mySound)
  
  return (
    <button onClick={() => playSound()}>
       Play Sound
    </button>
  )
}

用法示例2

在这个设置中,我们可以控制音量。另外,playSound()将在handleClick()函数中被调用,允许你在点击时做更多的事情,而不仅仅是播放声音。

function MyButton(){
  const [playSound] = useSound(mySound, { volume: 0.7 }) // 70% of the original volume
  
  const handleClick = () => {
    playSound()
    // maybe you want to add other things here?
  }

  return (
    <button onClick={() => handleClick()}>
       Play Sound
    </button>
  )
}

有关click herehere的更多信息

alen0pnh

alen0pnh3#

我在执行这个答案时遇到了一个不同的问题。
似乎浏览器在每次重新渲染时都在不断地尝试下载声音。
我最终使用useMemo作为音频,没有依赖项,这导致钩子只创建一次音频,而从不尝试重新创建它。

import {useMemo, useEffect, useState} from "react";

const useAudio = url => {
    const audio = useMemo(() => new Audio(url), []);
    const [playing, setPlaying] = useState(false);

    const toggle = () => setPlaying(!playing);

    useEffect(() => {
            playing ? audio.play() : audio.pause();
        },
        [playing]
    );

    useEffect(() => {
        audio.addEventListener('ended', () => setPlaying(false));
        return () => {
            audio.removeEventListener('ended', () => setPlaying(false));
        };
    }, []);

    return [playing, toggle];
};

export default useAudio;
ac1kyiln

ac1kyiln4#

我在使用Next JS时遇到了一些问题,因为音频是HTMLElement标记,最终,它给我带来了一个大错误,所以我决定进一步研究,在我的项目中的结果如下:

//inside your component function.
  const [audio] = useState( typeof Audio !== "undefined" && new Audio("your-url.mp3")); //this will prevent rendering errors on NextJS since NodeJs doesn't recognise HTML tags neither its libs.
  const [isPlaying, setIsPlaying] = useState(false);

为了处理播放器,我做了一个useEffect:

useEffect(() => {
    isPlaying ? audio.play() : audio.pause();
  }, [isPlaying]);

您将根据到目前为止创建的函数来管理状态“isPlaying”。

zqdjd7g9

zqdjd7g95#

未捕获的类型错误:无法读取未定义的属性"setState"
出现这个错误是因为this关键字在JavaScript中的工作方式。我认为如果我们解决了这个问题,音频应该可以播放得很好。
如果你在play()中执行console.log(this),你会发现this是未定义的,这就是为什么它会抛出那个错误,因为你在执行this.setState(),基本上thisplay()中的值取决于函数是如何调用的。

    • React有两种常见解决方案:**

1.使用bind()设置函数this的值,而不管它是如何调用的:

constructor(props) {
  super(props);
  this.play() = this.play.bind(this);
}

1.使用不提供自身此绑定的箭头函数

<button onClick={() => {this.play()}}>Play</button>

现在您将可以访问play()中的this.setStatethis.audio,对于pause()也是如此。

ia2d9nvy

ia2d9nvy6#

我来派对有点晚了,不过我还是背下了“托马斯·亨内斯”:
人们在研究这段代码时会遇到的一个问题是,如果你试图在一个有多个页面的应用中逐字逐句地使用这段代码,他们不会有一个愉快的时光,因为状态是在组件中管理的,你可以播放、导航和再次播放。
为了解决这个问题,您希望让组件将其状态推送到App.js并在那里管理状态。
请允许我说明我的意思。
我的播放器组件如下所示:

import React, { Component } from 'react'

class MusicPlayer extends Component {
  render() {
    const { playing } = this.props.player;

    return (
      <div>
        <button onClick={this.props.toggleMusic.bind(this, playing)}>{playing ? "Pause" : "Play"}</button>
      </div>
    );
  }
};

export default MusicPlayer;

然后在我的App.js中,它看起来像这样(使用TODO列表示例应用程序):

import React, { Component } from 'react';
import { BrowserRouter as Router, Route  } from 'react-router-dom'
import './App.css';
import Header from './componets/layout/Header'
import Todos from './componets/Todos'
import AddTodo from './componets/AddTodo'
import About from './componets/pages/About'
import MusicPlayer from './componets/MusicPlayer'
import axios from 'axios';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { playing: false, todos: [] }
    this.audio = new Audio('<YOUR MP3 LINK HERE>');
  }

  componentDidMount(){
    axios.get('https://jsonplaceholder.typicode.com/todos')
      .then(res => this.setState({ playing: this.state.playing, todos: res.data }))
  }

  toggleComplete = (id) => {
    this.setState({ playing: this.state.playing, todos: this.state.todos.map(todo => {
      if (todo.id === id){
        todo.completed = !todo.completed
      }
      return todo
    }) });
  }

  delTodo = (id) => {
    axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`)
      .then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos.filter(todo => todo.id !== id)] }));
  }

  addTodo = (title) => {
    axios.post('https://jsonplaceholder.typicode.com/todos', {
      title,
      completed: false
    })
      .then(res => this.setState({ playing: this.state.playing, todos: [...this.state.todos, res.data]}))

  }

  toggleMusic = () => {
    this.setState({ playing: !this.state.playing, todos: this.state.todos}, () => {
      this.state.playing ? this.audio.play() : this.audio.pause();
    });
  }

  render() {
    return (
      <Router>
        <div className="App">
          <div className="container">
            <Header />
            <Route exact path="/" render={props => (
              <React.Fragment>
                <AddTodo addTodo={this.addTodo} />
                <Todos todos={this.state.todos} toggleComplete={this.toggleComplete} delTodo={this.delTodo} />
              </React.Fragment>
            )} />
            <Route path="/About" render={props => (
              <React.Fragment>
                <About />
                <MusicPlayer player={this.state} toggleMusic={this.toggleMusic} />
              </React.Fragment>
            )} />
          </div>
        </div>
      </Router>
    );
  }
}

export default App;
8ftvxx2r

8ftvxx2r7#

你可以试试这个,对我有用

var tinung = `${window.location.origin}/terimakasih.ogg`;
                        var audio = document.createElement("audio");

                        audio.autoplay = true;
                        audio.load();
                        audio.addEventListener(
                            "load",
                            function() {
                                audio.play();
                            },
                            true
                        );
                        audio.src = tinung;

相关问题