w

示例

M3U8 在线播放器的实用示例和代码片段。

基本使用示例

简单播放器

创建一个基本的 M3U8 播放器:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>M3U8 播放器示例</title>
    <style>
      .player-container {
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }

      .video-player {
        width: 100%;
        height: auto;
        border-radius: 8px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
      }

      .controls {
        margin-top: 20px;
        display: flex;
        gap: 10px;
        align-items: center;
      }

      .url-input {
        flex: 1;
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 14px;
      }

      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
        transition: background-color 0.3s;
      }

      .btn-primary {
        background-color: #007bff;
        color: white;
      }

      .btn-primary:hover {
        background-color: #0056b3;
      }

      .btn-secondary {
        background-color: #6c757d;
        color: white;
      }

      .btn-secondary:hover {
        background-color: #545b62;
      }
    </style>
  </head>
  <body>
    <div class="player-container">
      <h1>M3U8 在线播放器</h1>

      <video id="videoPlayer" class="video-player" controls>您的浏览器不支持视频播放。</video>

      <div class="controls">
        <input
          type="text"
          id="urlInput"
          class="url-input"
          placeholder="输入 M3U8 流地址..."
          value="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8"
        />
        <button id="loadBtn" class="btn btn-primary">加载</button>
        <button id="playBtn" class="btn btn-secondary">播放</button>
        <button id="pauseBtn" class="btn btn-secondary">暂停</button>
      </div>

      <div
        id="status"
        style="margin-top: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 4px;"
      >
        状态: 就绪
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <script>
      class M3U8Player {
        constructor(videoElement, statusElement) {
          this.video = videoElement;
          this.status = statusElement;
          this.hls = null;
          this.currentUrl = '';

          this.init();
        }

        init() {
          // 检查浏览器支持
          if (Hls.isSupported()) {
            console.log('HLS.js 支持此浏览器');
          } else if (this.video.canPlayType('application/vnd.apple.mpegurl')) {
            console.log('原生 HLS 支持');
          } else {
            this.updateStatus('错误: 浏览器不支持 HLS 播放');
            return;
          }

          // 绑定事件
          this.bindEvents();
        }

        bindEvents() {
          // 视频事件
          this.video.addEventListener('loadstart', () => {
            this.updateStatus('开始加载...');
          });

          this.video.addEventListener('loadedmetadata', () => {
            this.updateStatus('元数据加载完成');
          });

          this.video.addEventListener('canplay', () => {
            this.updateStatus('可以播放');
          });

          this.video.addEventListener('play', () => {
            this.updateStatus('正在播放');
          });

          this.video.addEventListener('pause', () => {
            this.updateStatus('已暂停');
          });

          this.video.addEventListener('error', (e) => {
            this.updateStatus('播放错误: ' + e.error.message);
          });

          // 控制按钮事件
          document.getElementById('loadBtn').addEventListener('click', () => {
            const url = document.getElementById('urlInput').value.trim();
            if (url) {
              this.loadStream(url);
            }
          });

          document.getElementById('playBtn').addEventListener('click', () => {
            this.video.play();
          });

          document.getElementById('pauseBtn').addEventListener('click', () => {
            this.video.pause();
          });
        }

        loadStream(url) {
          this.currentUrl = url;
          this.updateStatus('正在加载流...');

          // 清理之前的 HLS 实例
          if (this.hls) {
            this.hls.destroy();
          }

          if (Hls.isSupported()) {
            // 使用 HLS.js
            this.hls = new Hls({
              debug: false,
              enableWorker: true,
              lowLatencyMode: false,
              backBufferLength: 90,
            });

            this.hls.loadSource(url);
            this.hls.attachMedia(this.video);

            this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
              this.updateStatus('流加载完成,可以播放');
            });

            this.hls.on(Hls.Events.ERROR, (event, data) => {
              console.error('HLS 错误:', data);
              if (data.fatal) {
                this.updateStatus('HLS 错误: ' + data.details);
              }
            });
          } else if (this.video.canPlayType('application/vnd.apple.mpegurl')) {
            // 使用原生 HLS 支持
            this.video.src = url;
            this.video.addEventListener('loadedmetadata', () => {
              this.updateStatus('流加载完成,可以播放');
            });
          }
        }

        updateStatus(message) {
          this.status.textContent = '状态: ' + message;
          console.log('状态更新:', message);
        }
      }

      // 初始化播放器
      const videoElement = document.getElementById('videoPlayer');
      const statusElement = document.getElementById('status');
      const player = new M3U8Player(videoElement, statusElement);
    </script>
  </body>
</html>

高级功能示例

带质量选择的播放器

创建一个支持质量级别选择的播放器:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>高级 M3U8 播放器</title>
    <style>
      .player-container {
        max-width: 1000px;
        margin: 0 auto;
        padding: 20px;
      }

      .video-player {
        width: 100%;
        height: auto;
        border-radius: 8px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
      }

      .controls {
        margin-top: 20px;
        display: grid;
        grid-template-columns: 1fr auto auto auto;
        gap: 10px;
        align-items: center;
      }

      .url-input {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 14px;
      }

      .quality-select {
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 14px;
        background-color: white;
      }

      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        font-size: 14px;
        transition: background-color 0.3s;
      }

      .btn-primary {
        background-color: #007bff;
        color: white;
      }

      .btn-primary:hover {
        background-color: #0056b3;
      }

      .info-panel {
        margin-top: 20px;
        padding: 15px;
        background-color: #f8f9fa;
        border-radius: 8px;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 15px;
      }

      .info-item {
        display: flex;
        justify-content: space-between;
        padding: 5px 0;
        border-bottom: 1px solid #e9ecef;
      }

      .info-item:last-child {
        border-bottom: none;
      }

      .info-label {
        font-weight: bold;
        color: #495057;
      }

      .info-value {
        color: #6c757d;
      }
    </style>
  </head>
  <body>
    <div class="player-container">
      <h1>高级 M3U8 播放器</h1>

      <video id="videoPlayer" class="video-player" controls>您的浏览器不支持视频播放。</video>

      <div class="controls">
        <input
          type="text"
          id="urlInput"
          class="url-input"
          placeholder="输入 M3U8 流地址..."
          value="https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8"
        />
        <select id="qualitySelect" class="quality-select">
          <option value="-1">自动</option>
        </select>
        <button id="loadBtn" class="btn btn-primary">加载</button>
        <button id="reloadBtn" class="btn btn-primary">重新加载</button>
      </div>

      <div class="info-panel">
        <div class="info-item">
          <span class="info-label">状态:</span>
          <span id="status" class="info-value">就绪</span>
        </div>
        <div class="info-item">
          <span class="info-label">当前质量:</span>
          <span id="currentQuality" class="info-value">-</span>
        </div>
        <div class="info-item">
          <span class="info-label">可用质量:</span>
          <span id="availableQualities" class="info-value">-</span>
        </div>
        <div class="info-item">
          <span class="info-label">当前时间:</span>
          <span id="currentTime" class="info-value">00:00</span>
        </div>
        <div class="info-item">
          <span class="info-label">总时长:</span>
          <span id="duration" class="info-value">00:00</span>
        </div>
        <div class="info-item">
          <span class="info-label">缓冲进度:</span>
          <span id="bufferProgress" class="info-value">0%</span>
        </div>
      </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <script>
      class AdvancedM3U8Player {
        constructor(videoElement) {
          this.video = videoElement;
          this.hls = null;
          this.currentUrl = '';
          this.qualityLevels = [];

          this.init();
        }

        init() {
          if (!Hls.isSupported() && !this.video.canPlayType('application/vnd.apple.mpegurl')) {
            this.updateStatus('错误: 浏览器不支持 HLS 播放');
            return;
          }

          this.bindEvents();
          this.startTimeUpdate();
        }

        bindEvents() {
          // 视频事件
          this.video.addEventListener('loadstart', () => {
            this.updateStatus('开始加载...');
          });

          this.video.addEventListener('loadedmetadata', () => {
            this.updateStatus('元数据加载完成');
            this.updateDuration();
          });

          this.video.addEventListener('canplay', () => {
            this.updateStatus('可以播放');
          });

          this.video.addEventListener('play', () => {
            this.updateStatus('正在播放');
          });

          this.video.addEventListener('pause', () => {
            this.updateStatus('已暂停');
          });

          this.video.addEventListener('error', (e) => {
            this.updateStatus('播放错误: ' + e.error.message);
          });

          // 控制按钮事件
          document.getElementById('loadBtn').addEventListener('click', () => {
            const url = document.getElementById('urlInput').value.trim();
            if (url) {
              this.loadStream(url);
            }
          });

          document.getElementById('reloadBtn').addEventListener('click', () => {
            if (this.currentUrl) {
              this.loadStream(this.currentUrl);
            }
          });

          // 质量选择事件
          document.getElementById('qualitySelect').addEventListener('change', (e) => {
            const level = parseInt(e.target.value);
            this.setQualityLevel(level);
          });
        }

        loadStream(url) {
          this.currentUrl = url;
          this.updateStatus('正在加载流...');

          // 清理之前的 HLS 实例
          if (this.hls) {
            this.hls.destroy();
          }

          if (Hls.isSupported()) {
            this.hls = new Hls({
              debug: false,
              enableWorker: true,
              lowLatencyMode: false,
              backBufferLength: 90,
              maxBufferLength: 30,
              maxMaxBufferLength: 600,
            });

            this.hls.loadSource(url);
            this.hls.attachMedia(this.video);

            this.hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
              this.updateStatus('流加载完成,可以播放');
              this.updateQualityLevels(data.levels);
            });

            this.hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
              this.updateCurrentQuality(data.level);
            });

            this.hls.on(Hls.Events.ERROR, (event, data) => {
              console.error('HLS 错误:', data);
              if (data.fatal) {
                this.updateStatus('HLS 错误: ' + data.details);
              }
            });
          } else if (this.video.canPlayType('application/vnd.apple.mpegurl')) {
            this.video.src = url;
            this.video.addEventListener('loadedmetadata', () => {
              this.updateStatus('流加载完成,可以播放');
            });
          }
        }

        updateQualityLevels(levels) {
          this.qualityLevels = levels;
          const select = document.getElementById('qualitySelect');

          // 清空现有选项(保留"自动"选项)
          select.innerHTML = '<option value="-1">自动</option>';

          // 添加质量级别选项
          levels.forEach((level, index) => {
            const option = document.createElement('option');
            option.value = index;
            option.textContent = this.formatQualityLevel(level);
            select.appendChild(option);
          });

          this.updateAvailableQualities();
        }

        formatQualityLevel(level) {
          if (level.height) {
            return `${level.height}p (${Math.round(level.bitrate / 1000)}kbps)`;
          } else if (level.bitrate) {
            return `${Math.round(level.bitrate / 1000)}kbps`;
          } else {
            return `Level ${level.level}`;
          }
        }

        setQualityLevel(level) {
          if (this.hls && level >= 0 && level < this.qualityLevels.length) {
            this.hls.currentLevel = level;
            this.updateCurrentQuality(level);
          } else if (level === -1) {
            this.hls.currentLevel = -1;
            this.updateCurrentQuality(-1);
          }
        }

        updateCurrentQuality(level) {
          const currentQualityElement = document.getElementById('currentQuality');
          if (level === -1) {
            currentQualityElement.textContent = '自动';
          } else if (this.qualityLevels[level]) {
            currentQualityElement.textContent = this.formatQualityLevel(this.qualityLevels[level]);
          }
        }

        updateAvailableQualities() {
          const availableQualitiesElement = document.getElementById('availableQualities');
          const count = this.qualityLevels.length;
          availableQualitiesElement.textContent = `${count} 个质量级别`;
        }

        updateDuration() {
          const durationElement = document.getElementById('duration');
          if (this.video.duration && !isNaN(this.video.duration)) {
            durationElement.textContent = this.formatTime(this.video.duration);
          }
        }

        startTimeUpdate() {
          setInterval(() => {
            this.updateCurrentTime();
            this.updateBufferProgress();
          }, 1000);
        }

        updateCurrentTime() {
          const currentTimeElement = document.getElementById('currentTime');
          if (this.video.currentTime && !isNaN(this.video.currentTime)) {
            currentTimeElement.textContent = this.formatTime(this.video.currentTime);
          }
        }

        updateBufferProgress() {
          const bufferProgressElement = document.getElementById('bufferProgress');
          if (this.video.buffered.length > 0) {
            const bufferedEnd = this.video.buffered.end(this.video.buffered.length - 1);
            const duration = this.video.duration;
            if (duration > 0) {
              const progress = (bufferedEnd / duration) * 100;
              bufferProgressElement.textContent = `${Math.round(progress)}%`;
            }
          }
        }

        formatTime(seconds) {
          const hours = Math.floor(seconds / 3600);
          const minutes = Math.floor((seconds % 3600) / 60);
          const secs = Math.floor(seconds % 60);

          if (hours > 0) {
            return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
          } else {
            return `${minutes}:${secs.toString().padStart(2, '0')}`;
          }
        }

        updateStatus(message) {
          document.getElementById('status').textContent = message;
          console.log('状态更新:', message);
        }
      }

      // 初始化播放器
      const videoElement = document.getElementById('videoPlayer');
      const player = new AdvancedM3U8Player(videoElement);
    </script>
  </body>
</html>

React 组件示例

React M3U8 播放器组件

import React, { useState, useEffect, useRef } from 'react';
import Hls from 'hls.js';

const M3U8Player = ({ url, autoplay = false, controls = true }) => {
  const videoRef = useRef(null);
  const hlsRef = useRef(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [qualityLevels, setQualityLevels] = useState([]);
  const [currentQuality, setCurrentQuality] = useState(-1);

  useEffect(() => {
    const video = videoRef.current;
    if (!video) return;

    // 检查浏览器支持
    if (Hls.isSupported()) {
      console.log('使用 HLS.js');
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      console.log('使用原生 HLS 支持');
    } else {
      setError('浏览器不支持 HLS 播放');
      return;
    }

    // 绑定视频事件
    const handleLoadStart = () => setIsLoading(true);
    const handleLoadedMetadata = () => {
      setIsLoading(false);
      setDuration(video.duration);
    };
    const handleTimeUpdate = () => setCurrentTime(video.currentTime);
    const handlePlay = () => setIsPlaying(true);
    const handlePause = () => setIsPlaying(false);
    const handleError = (e) => {
      setError(`播放错误: ${e.error.message}`);
      setIsLoading(false);
    };

    video.addEventListener('loadstart', handleLoadStart);
    video.addEventListener('loadedmetadata', handleLoadedMetadata);
    video.addEventListener('timeupdate', handleTimeUpdate);
    video.addEventListener('play', handlePlay);
    video.addEventListener('pause', handlePause);
    video.addEventListener('error', handleError);

    return () => {
      video.removeEventListener('loadstart', handleLoadStart);
      video.removeEventListener('loadedmetadata', handleLoadedMetadata);
      video.removeEventListener('timeupdate', handleTimeUpdate);
      video.removeEventListener('play', handlePlay);
      video.removeEventListener('pause', handlePause);
      video.removeEventListener('error', handleError);
    };
  }, []);

  useEffect(() => {
    if (!url) return;

    const video = videoRef.current;
    if (!video) return;

    // 清理之前的 HLS 实例
    if (hlsRef.current) {
      hlsRef.current.destroy();
      hlsRef.current = null;
    }

    setError(null);
    setIsLoading(true);

    if (Hls.isSupported()) {
      const hls = new Hls({
        debug: false,
        enableWorker: true,
        lowLatencyMode: false,
        backBufferLength: 90,
      });

      hls.loadSource(url);
      hls.attachMedia(video);

      hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
        setIsLoading(false);
        setQualityLevels(data.levels);
        if (autoplay) {
          video.play();
        }
      });

      hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
        setCurrentQuality(data.level);
      });

      hls.on(Hls.Events.ERROR, (event, data) => {
        console.error('HLS 错误:', data);
        if (data.fatal) {
          setError(`HLS 错误: ${data.details}`);
          setIsLoading(false);
        }
      });

      hlsRef.current = hls;
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = url;
      if (autoplay) {
        video.play();
      }
    }

    return () => {
      if (hlsRef.current) {
        hlsRef.current.destroy();
        hlsRef.current = null;
      }
    };
  }, [url, autoplay]);

  const handleQualityChange = (level) => {
    if (hlsRef.current && level >= 0 && level < qualityLevels.length) {
      hlsRef.current.currentLevel = level;
      setCurrentQuality(level);
    } else if (level === -1) {
      hlsRef.current.currentLevel = -1;
      setCurrentQuality(-1);
    }
  };

  const formatTime = (seconds) => {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const secs = Math.floor(seconds % 60);

    if (hours > 0) {
      return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
    } else {
      return `${minutes}:${secs.toString().padStart(2, '0')}`;
    }
  };

  const formatQualityLevel = (level) => {
    if (level.height) {
      return `${level.height}p (${Math.round(level.bitrate / 1000)}kbps)`;
    } else if (level.bitrate) {
      return `${Math.round(level.bitrate / 1000)}kbps`;
    } else {
      return `Level ${level.level}`;
    }
  };

  return (
    <div className="m3u8-player">
      <video ref={videoRef} controls={controls} style={{ width: '100%', height: 'auto' }}>
        您的浏览器不支持视频播放。
      </video>

      {isLoading && <div className="loading">正在加载...</div>}

      {error && (
        <div className="error" style={{ color: 'red', marginTop: '10px' }}>
          {error}
        </div>
      )}

      {qualityLevels.length > 0 && (
        <div className="quality-controls" style={{ marginTop: '10px' }}>
          <label htmlFor="quality-select">质量选择: </label>
          <select
            id="quality-select"
            value={currentQuality}
            onChange={(e) => handleQualityChange(parseInt(e.target.value))}
          >
            <option value={-1}>自动</option>
            {qualityLevels.map((level, index) => (
              <option key={index} value={index}>
                {formatQualityLevel(level)}
              </option>
            ))}
          </select>
        </div>
      )}

      <div className="player-info" style={{ marginTop: '10px', fontSize: '14px', color: '#666' }}>
        <span>
          时间: {formatTime(currentTime)} / {formatTime(duration)}
        </span>
        {isPlaying ? <span> | 正在播放</span> : <span> | 已暂停</span>}
      </div>
    </div>
  );
};

export default M3U8Player;

使用 React 组件

import React, { useState } from 'react';
import M3U8Player from './M3U8Player';

const App = () => {
  const [streamUrl, setStreamUrl] = useState(
    'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8',
  );

  return (
    <div className="app">
      <h1>M3U8 播放器示例</h1>

      <div style={{ marginBottom: '20px' }}>
        <input
          type="text"
          value={streamUrl}
          onChange={(e) => setStreamUrl(e.target.value)}
          placeholder="输入 M3U8 流地址..."
          style={{ width: '100%', padding: '10px', marginBottom: '10px' }}
        />
      </div>

      <M3U8Player url={streamUrl} autoplay={false} controls={true} />
    </div>
  );
};

export default App;

Vue.js 组件示例

Vue M3U8 播放器组件

<template>
  <div class="m3u8-player">
    <video
      ref="videoElement"
      :controls="controls"
      :autoplay="autoplay"
      style="width: 100%; height: auto;"
    >
      您的浏览器不支持视频播放。
    </video>

    <div v-if="isLoading" class="loading">正在加载...</div>

    <div v-if="error" class="error" style="color: red; margin-top: 10px;">
      {{ error }}
    </div>

    <div v-if="qualityLevels.length > 0" class="quality-controls" style="margin-top: 10px;">
      <label for="quality-select">质量选择: </label>
      <select id="quality-select" :value="currentQuality" @change="handleQualityChange">
        <option value="-1">自动</option>
        <option v-for="(level, index) in qualityLevels" :key="index" :value="index">
          {{ formatQualityLevel(level) }}
        </option>
      </select>
    </div>

    <div class="player-info" style="margin-top: 10px; font-size: 14px; color: #666;">
      <span>时间: {{ formatTime(currentTime) }} / {{ formatTime(duration) }}</span>
      <span v-if="isPlaying"> | 正在播放</span>
      <span v-else> | 已暂停</span>
    </div>
  </div>
</template>

<script>
import Hls from 'hls.js';

export default {
  name: 'M3U8Player',
  props: {
    url: {
      type: String,
      required: true,
    },
    autoplay: {
      type: Boolean,
      default: false,
    },
    controls: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      hls: null,
      isLoading: false,
      error: null,
      currentTime: 0,
      duration: 0,
      isPlaying: false,
      qualityLevels: [],
      currentQuality: -1,
    };
  },
  mounted() {
    this.initPlayer();
  },
  beforeUnmount() {
    this.destroyPlayer();
  },
  watch: {
    url: {
      handler(newUrl) {
        if (newUrl) {
          this.loadStream(newUrl);
        }
      },
      immediate: true,
    },
  },
  methods: {
    initPlayer() {
      const video = this.$refs.videoElement;
      if (!video) return;

      // 检查浏览器支持
      if (Hls.isSupported()) {
        console.log('使用 HLS.js');
      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        console.log('使用原生 HLS 支持');
      } else {
        this.error = '浏览器不支持 HLS 播放';
        return;
      }

      // 绑定视频事件
      video.addEventListener('loadstart', this.handleLoadStart);
      video.addEventListener('loadedmetadata', this.handleLoadedMetadata);
      video.addEventListener('timeupdate', this.handleTimeUpdate);
      video.addEventListener('play', this.handlePlay);
      video.addEventListener('pause', this.handlePause);
      video.addEventListener('error', this.handleError);
    },

    destroyPlayer() {
      if (this.hls) {
        this.hls.destroy();
        this.hls = null;
      }
    },

    loadStream(url) {
      const video = this.$refs.videoElement;
      if (!video) return;

      this.destroyPlayer();
      this.error = null;
      this.isLoading = true;

      if (Hls.isSupported()) {
        this.hls = new Hls({
          debug: false,
          enableWorker: true,
          lowLatencyMode: false,
          backBufferLength: 90,
        });

        this.hls.loadSource(url);
        this.hls.attachMedia(video);

        this.hls.on(Hls.Events.MANIFEST_PARSED, (event, data) => {
          this.isLoading = false;
          this.qualityLevels = data.levels;
          if (this.autoplay) {
            video.play();
          }
        });

        this.hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
          this.currentQuality = data.level;
        });

        this.hls.on(Hls.Events.ERROR, (event, data) => {
          console.error('HLS 错误:', data);
          if (data.fatal) {
            this.error = `HLS 错误: ${data.details}`;
            this.isLoading = false;
          }
        });
      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        video.src = url;
        if (this.autoplay) {
          video.play();
        }
      }
    },

    handleQualityChange(event) {
      const level = parseInt(event.target.value);
      if (this.hls && level >= 0 && level < this.qualityLevels.length) {
        this.hls.currentLevel = level;
        this.currentQuality = level;
      } else if (level === -1) {
        this.hls.currentLevel = -1;
        this.currentQuality = -1;
      }
    },

    handleLoadStart() {
      this.isLoading = true;
    },

    handleLoadedMetadata() {
      this.isLoading = false;
      this.duration = this.$refs.videoElement.duration;
    },

    handleTimeUpdate() {
      this.currentTime = this.$refs.videoElement.currentTime;
    },

    handlePlay() {
      this.isPlaying = true;
    },

    handlePause() {
      this.isPlaying = false;
    },

    handleError(event) {
      this.error = `播放错误: ${event.error.message}`;
      this.isLoading = false;
    },

    formatTime(seconds) {
      const hours = Math.floor(seconds / 3600);
      const minutes = Math.floor((seconds % 3600) / 60);
      const secs = Math.floor(seconds % 60);

      if (hours > 0) {
        return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
      } else {
        return `${minutes}:${secs.toString().padStart(2, '0')}`;
      }
    },

    formatQualityLevel(level) {
      if (level.height) {
        return `${level.height}p (${Math.round(level.bitrate / 1000)}kbps)`;
      } else if (level.bitrate) {
        return `${Math.round(level.bitrate / 1000)}kbps`;
      } else {
        return `Level ${level.level}`;
      }
    },
  },
};
</script>

<style scoped>
.m3u8-player {
  max-width: 800px;
  margin: 0 auto;
}

.loading {
  text-align: center;
  padding: 20px;
  color: #666;
}

.error {
  padding: 10px;
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
  border-radius: 4px;
  color: #721c24;
}

.quality-controls {
  display: flex;
  align-items: center;
  gap: 10px;
}

.quality-controls select {
  padding: 5px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.player-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

这些示例展示了如何在不同框架中集成和使用 M3U8 播放器,包括基本功能、高级功能和质量选择等特性。开发者可以根据自己的需求选择合适的示例进行参考和修改。

这个页面对您有帮助吗?