You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用vidstack/react动态播放音频遇media未就绪错误的解决咨询

问题:Vidstack React 动态设置音频src并播放时触发"media is not ready"错误

在React项目中使用vidstack/react组件,通过外部按钮调用playbackService动态设置音频src并播放时,遇到以下错误:

Error: Error: [vidstack] media is not ready - wait for can-play event.

原Player组件代码:

import '@vidstack/react/player/styles/default/theme.css';
import '@vidstack/react/player/styles/default/layouts/audio.css';

import { useEffect, useRef, useState } from 'react';

import {
  MediaPlayer,
  MediaProvider,
  type MediaPlayerInstance,
} from '@vidstack/react';

import {
  DefaultAudioLayout,
  defaultLayoutIcons,
} from '@vidstack/react/player/layouts/default';
import { playbackService } from './playbackService';

export function Player() {
  const [src, setSrc] = useState('');
  const playerRef = useRef<MediaPlayerInstance>(null);

  const handleClick = () => {
    playbackService.playSong();
  };

  const initPlaybackRelatedServices = () => {
    const playerInstance = playerRef.current;

    if (playerInstance) {
      playbackService.init({ media: playerInstance });
    }
  };

  useEffect(() => {
    if (playerRef.current) {
      console.log('set player instance initPlaybackRelatedServices');
      initPlaybackRelatedServices();
    }
  }, []);

  return (
    <>
      <button onClick={handleClick}>Click to play audio</button>
      <MediaPlayer title="" viewType="audio" src="" ref={playerRef}>
        <MediaProvider />
        <DefaultAudioLayout icons={defaultLayoutIcons} />
      </MediaPlayer>
    </>
  );
}

原playbackService代码:

import type { MediaPlayerInstance } from '@vidstack/react';

interface AudioPlayerProvider {
  media: MediaPlayerInstance;
}

class PlayerService {
  private provider: AudioPlayerProvider | null = null;

  public playSong() {
    if (this.provider && this.provider.media) {
      this.provider.media.src =
        'https://521dimensions.com/song/FirstSnow-Emancipator.mp3';
      this.provider?.media?.play();
      console.log('Waiting for media to be ready...');
    } else {
      console.log('Player or media instance is not available.');
    }
  }

  public init(provider: AudioPlayerProvider) {
    this.provider = provider;
    console.log('AudioPlayerService initialized with player:', provider.media);
   
  }

  public getMedia() {
    return this.provider?.media;
  }

  public play() {
    this.provider?.media.play();
  }

  public pause() {
    this.provider?.media.pause();
  }
}

export const playbackService = new PlayerService();

尝试监听can-play事件但未触发,修改后的playSong方法:

public playSong() {
    if (this.provider && this.provider.media) {
        const media = this.provider.media;

        media.src = 'https://521dimensions.com/song/FirstSnow-Emancipator.mp3';

        const handleCanPlay = () => {
            console.log('Media is ready, playing now.');
            media.play();
            media.removeEventListener('can-play', handleCanPlay); // Clean up
        };

        media.addEventListener('can-play', handleCanPlay);
    } else {
        console.log('Player or media instance is not available.');
    }
}

解决方案

核心问题分析

  1. Vidstack的MediaPlayerInstance不使用原生DOM事件监听方式,直接调用addEventListener无法正确捕获内部事件。
  2. 设置src后未主动触发媒体加载流程,导致can-play事件不会触发。
  3. 初始化时机不可靠,useEffect可能在player实例未完全就绪时执行。

修改步骤

1. 使用Vidstack官方事件API监听can-play

Vidstack提供了$on和$off方法用于订阅/取消订阅媒体事件,替代原生的事件监听方法。

2. 设置src后主动调用load()

设置音频src后必须调用load()启动加载流程,否则媒体不会开始缓冲,无法触发can-play事件。

3. 优化Player组件初始化时机

使用MediaPlayer的onReady回调初始化playbackService,确保player实例完全就绪后再传递给服务。

4. 添加事件清理逻辑

避免重复绑定事件导致的多次触发问题,在订阅新事件前移除旧的监听。


修改后的完整代码

Player组件

import '@vidstack/react/player/styles/default/theme.css';
import '@vidstack/react/player/styles/default/layouts/audio.css';

import { useRef } from 'react';

import {
  MediaPlayer,
  MediaProvider,
  type MediaPlayerInstance,
} from '@vidstack/react';

import {
  DefaultAudioLayout,
  defaultLayoutIcons,
} from '@vidstack/react/player/layouts/default';
import { playbackService } from './playbackService';

export function Player() {
  const playerRef = useRef<MediaPlayerInstance>(null);

  const handleClick = () => {
    playbackService.playSong();
  };

  return (
    <>
      <button onClick={handleClick}>Click to play audio</button>
      <MediaPlayer 
        title="" 
        viewType="audio" 
        src="" 
        ref={playerRef}
        onReady={(player) => {
          playbackService.init({ media: player });
          console.log('Player ready, initialized playback service');
        }}
      >
        <MediaProvider />
        <DefaultAudioLayout icons={defaultLayoutIcons} />
      </MediaPlayer>
    </>
  );
}

playbackService

import type { MediaPlayerInstance } from '@vidstack/react';

interface AudioPlayerProvider {
  media: MediaPlayerInstance;
}

class PlayerService {
  private provider: AudioPlayerProvider | null = null;
  private canPlayHandler: (() => void) | null = null;

  public playSong() {
    if (!this.provider?.media) {
      console.log('Player or media instance is not available.');
      return;
    }

    const media = this.provider.media;

    // 清理之前的事件监听
    if (this.canPlayHandler) {
      media.$off('can-play', this.canPlayHandler);
    }

    // 定义can-play事件处理函数
    this.canPlayHandler = () => {
      console.log('Media is ready, starting playback');
      media.play();
      // 播放完成后清理监听
      media.$off('can-play', this.canPlayHandler!);
      this.canPlayHandler = null;
    };

    // 订阅can-play事件
    media.$on('can-play', this.canPlayHandler);

    // 设置音频地址并启动加载
    media.src = 'https://521dimensions.com/song/FirstSnow-Emancipator.mp3';
    media.load();
  }

  public init(provider: AudioPlayerProvider) {
    this.provider = provider;
    console.log('AudioPlayerService initialized with player:', provider.media);
  }

  public getMedia() {
    return this.provider?.media;
  }

  public play() {
    this.provider?.media.play();
  }

  public pause() {
    this.provider?.media.pause();
  }
}

export const playbackService = new PlayerService();

内容的提问来源于stack exchange,提问作者Rasik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:05:06