w

範例程式碼

M3U8 線上播放器的實作範例和範例程式碼。

基本 HTML/JavaScript 範例

簡單播放器

<!DOCTYPE html>
<html lang="zh-Hant">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>M3U8 播放器 - 基本範例</title>
    <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet" />
  </head>
  <body>
    <div class="container">
      <h1>M3U8 串流播放器</h1>

      <!-- 播放器容器 -->
      <video-js
        id="m3u8-player"
        class="vjs-default-skin"
        controls
        preload="auto"
        width="800"
        height="450"
        data-setup="{}"
      >
        <p class="vjs-no-js">
          要播放此影片,請啟用 JavaScript。
          <a href="https://videojs.com/html5-video-support/" target="_blank">
            支援 Video.js 的瀏覽器 </a
          >        </p>
      </video-js>

      <!-- 控制面板 -->
      <div class="controls">
        <input type="url" id="stream-url" placeholder="輸入 M3U8 URL" />
        <button id="load-btn">載入</button>
        <button id="play-btn">播放</button>
        <button id="pause-btn">暫停</button>
      </div>

      <!-- 資訊 -->
      <div class="info">
        <p>目前時間: <span id="current-time">0:00</span></p>
        <p>總時間: <span id="duration">0:00</span></p>
        <p>品質: <span id="quality">自動</span></p>
      </div>
    </div>

    <script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@videojs/http-streaming@3.0.2/dist/videojs-http-streaming.min.js"></script>
    <script>
      // 初始化播放器
      const player = videojs('m3u8-player', {
        html5: {
          vhs: {
            overrideNative: true,
          },
        },
      });

      // 取得 DOM 元素
      const streamUrlInput = document.getElementById('stream-url');
      const loadBtn = document.getElementById('load-btn');
      const playBtn = document.getElementById('play-btn');
      const pauseBtn = document.getElementById('pause-btn');
      const currentTimeSpan = document.getElementById('current-time');
      const durationSpan = document.getElementById('duration');
      const qualitySpan = document.getElementById('quality');

      // 設定事件監聽器
      loadBtn.addEventListener('click', loadStream);
      playBtn.addEventListener('click', () => player.play());
      pauseBtn.addEventListener('click', () => player.pause());

      // 載入串流函數
      function loadStream() {
        const url = streamUrlInput.value.trim();
        if (!url) {
          alert('請輸入 URL');
          return;
        }

        try {
          player.src({
            src: url,
            type: 'application/x-mpegURL',
          });
          console.log('串流已載入:', url);
        } catch (error) {
          console.error('串流載入錯誤:', error);
          alert('串流載入錯誤');
        }
      }

      // 時間格式化函數
      function 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')}`;
        }
      }

      // 監控播放器事件
      player.on('loadedmetadata', () => {
        durationSpan.textContent = formatTime(player.duration());
      });

      player.on('timeupdate', () => {
        currentTimeSpan.textContent = formatTime(player.currentTime());
      });

      player.on('error', (error) => {
        console.error('播放器錯誤:', error);
        alert('播放錯誤');
      });

      // 設定範例 URL
      streamUrlInput.value =
        'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8';
    </script>
  </body>
</html>

進階功能範例

品質選擇和錯誤處理

<!DOCTYPE html>
<html lang="zh-Hant">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>M3U8 播放器 - 進階範例</title>
    <link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet" />
    <style>
      .quality-selector {
        margin: 10px 0;
      }
      .quality-selector select {
        padding: 5px;
        margin-right: 10px;
      }
      .error-message {
        color: red;
        margin: 10px 0;
      }
      .stats {
        background: #f5f5f5;
        padding: 10px;
        margin: 10px 0;
        border-radius: 5px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>M3U8 播放器 - 進階功能</h1>

      <video-js
        id="advanced-player"
        class="vjs-default-skin"
        controls
        preload="auto"
        width="800"
        height="450"
        data-setup="{}"
      >
      </video-js>

      <!-- 品質選擇器 -->
      <div class="quality-selector">
        <label for="quality-select">選擇品質:</label>
        <select id="quality-select">
          <option value="auto">自動</option>
        </select>
      </div>

      <!-- 錯誤訊息 -->
      <div id="error-message" class="error-message" style="display: none;"></div>

      <!-- 統計資訊 -->
      <div class="stats">
        <h3>統計資訊</h3>
        <p>目前位元率: <span id="current-bitrate">-</span></p>
        <p>緩衝健康度: <span id="buffer-health">-</span></p>
        <p>網路狀態: <span id="network-state">-</span></p>
      </div>
    </div>

    <script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@videojs/http-streaming@3.0.2/dist/videojs-http-streaming.min.js"></script>
    <script>
      // 初始化播放器
      const player = videojs('advanced-player', {
        html5: {
          vhs: {
            overrideNative: true,
            enableLowInitialPlaylist: true,
            smoothQualityChange: true,
          },
        },
      });

      const qualitySelect = document.getElementById('quality-select');
      const errorMessage = document.getElementById('error-message');
      const currentBitrateSpan = document.getElementById('current-bitrate');
      const bufferHealthSpan = document.getElementById('buffer-health');
      const networkStateSpan = document.getElementById('network-state');

      // 載入範例串流
      player.src({
        src: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8',
        type: 'application/x-mpegURL',
      });

      // 當品質層級可用時
      player.on('loadedmetadata', () => {
        updateQualityOptions();
      });

      // 更新品質選項
      function updateQualityOptions() {
        const hls = player.tech().hls;
        if (hls && hls.levels) {
          // 清除現有選項
          qualitySelect.innerHTML = '<option value="auto">自動</option>';

          // 新增品質層級
          hls.levels.forEach((level, index) => {
            const option = document.createElement('option');
            option.value = index;
            option.textContent = `${level.height}p (${Math.round(level.bitrate / 1000)}kbps)`;
            qualitySelect.appendChild(option);
          });
        }
      }

      // 變更品質
      qualitySelect.addEventListener('change', (e) => {
        const hls = player.tech().hls;
        if (hls) {
          if (e.target.value === 'auto') {
            hls.currentLevel = -1; // 自動品質
          } else {
            hls.currentLevel = parseInt(e.target.value);
          }
        }
      });

      // 錯誤處理
      player.on('error', (error) => {
        console.error('播放器錯誤:', error);
        showError(`發生錯誤: ${error.message || '未知錯誤'}`);

        // 嘗試自動恢復
        setTimeout(() => {
          if (player.error()) {
            player.error(null);
            player.load();
          }
        }, 3000);
      });

      // 顯示錯誤訊息
      function showError(message) {
        errorMessage.textContent = message;
        errorMessage.style.display = 'block';
        setTimeout(() => {
          errorMessage.style.display = 'none';
        }, 5000);
      }

      // 更新統計資訊
      function updateStats() {
        const hls = player.tech().hls;
        if (hls) {
          const currentLevel = hls.levels[hls.currentLevel];
          if (currentLevel) {
            currentBitrateSpan.textContent = `${Math.round(currentLevel.bitrate / 1000)}kbps`;
          }

          // 計算緩衝健康度
          const buffered = player.buffered();
          const currentTime = player.currentTime();
          const duration = player.duration();

          if (buffered.length > 0) {
            const bufferedEnd = buffered.end(buffered.length - 1);
            const bufferHealth = ((bufferedEnd - currentTime) / duration) * 100;
            bufferHealthSpan.textContent = `${bufferHealth.toFixed(1)}%`;
          }
        }

        // 網路狀態
        const networkState = player.networkState();
        const states = ['初始狀態', '閒置', '載入中', '無來源'];
        networkStateSpan.textContent = states[networkState] || '未知';
      }

      // 定期更新統計
      setInterval(updateStats, 1000);

      // 初始統計更新
      player.on('canplay', updateStats);
    </script>
  </body>
</html>

React 元件範例

React 中的 M3U8 播放器

import React, { useEffect, useRef, useState } from 'react';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';

const M3U8Player = ({ src, options = {} }) => {
  const videoRef = useRef(null);
  const playerRef = useRef(null);
  const [error, setError] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    // 初始化播放器
    if (videoRef.current && !playerRef.current) {
      const videoElement = videoRef.current;

      const playerOptions = {
        controls: true,
        responsive: true,
        fluid: true,
        html5: {
          vhs: {
            overrideNative: true,
            enableLowInitialPlaylist: true,
            smoothQualityChange: true,
          },
        },
        ...options,
      };

      playerRef.current = videojs(videoElement, playerOptions);

      // 錯誤處理
      playerRef.current.on('error', (error) => {
        console.error('播放器錯誤:', error);
        setError('播放錯誤');
      });

      // 載入開始
      playerRef.current.on('loadstart', () => {
        setIsLoading(true);
        setError(null);
      });

      // 載入完成
      playerRef.current.on('canplay', () => {
        setIsLoading(false);
      });
    }

    return () => {
      if (playerRef.current) {
        playerRef.current.dispose();
        playerRef.current = null;
      }
    };
  }, []);

  // 當來源變更時
  useEffect(() => {
    if (playerRef.current && src) {
      playerRef.current.src({
        src: src,
        type: 'application/x-mpegURL',
      });
    }
  }, [src]);

  return (
    <div className="m3u8-player-container">
      {isLoading && (
        <div className="loading-overlay">
          <div className="loading-spinner">載入中...</div>
        </div>
      )}

      {error && <div className="error-message">{error}</div>}

      <div data-vjs-player>
        <video
          ref={videoRef}
          className="video-js vjs-default-skin"
          controls
          preload="auto"
          data-setup="{}"
        />
      </div>
    </div>
  );
};

// 使用範例
const App = () => {
  const [streamUrl, setStreamUrl] = useState('');
  const [currentUrl, setCurrentUrl] = useState('');

  const handleLoadStream = () => {
    if (streamUrl.trim()) {
      setCurrentUrl(streamUrl);
    }
  };

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

      <div className="controls">
        <input
          type="url"
          value={streamUrl}
          onChange={(e) => setStreamUrl(e.target.value)}
          placeholder="輸入 M3U8 URL"
        />
        <button onClick={handleLoadStream}>載入</button>
      </div>

      {currentUrl && (
        <M3U8Player
          src={currentUrl}
          options={{
            width: 800,
            height: 450,
          }}
        />
      )}
    </div>
  );
};

export default App;

Vue.js 元件範例

Vue.js 中的 M3U8 播放器

<template>
  <div class="m3u8-player-wrapper">
    <div class="controls">
      <input v-model="streamUrl" type="url" placeholder="輸入 M3U8 URL" @keyup.enter="loadStream" />
      <button @click="loadStream" :disabled="!streamUrl.trim()">載入</button>
    </div>

    <div v-if="error" class="error-message">
      {{ error }}
    </div>

    <div v-if="isLoading" class="loading-message">載入中...</div>

    <div ref="playerContainer" class="player-container"></div>
  </div>
</template>

<script>
import videojs from 'video.js';
import 'video.js/dist/video-js.css';

export default {
  name: 'M3U8Player',
  props: {
    initialUrl: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      streamUrl: this.initialUrl,
      player: null,
      error: null,
      isLoading: false,
    };
  },
  mounted() {
    this.initializePlayer();
    if (this.initialUrl) {
      this.loadStream();
    }
  },
  beforeUnmount() {
    if (this.player) {
      this.player.dispose();
    }
  },
  methods: {
    initializePlayer() {
      const videoElement = document.createElement('video');
      videoElement.className = 'video-js vjs-default-skin';
      videoElement.controls = true;
      videoElement.preload = 'auto';
      videoElement.width = 800;
      videoElement.height = 450;

      this.$refs.playerContainer.appendChild(videoElement);

      this.player = videojs(videoElement, {
        html5: {
          vhs: {
            overrideNative: true,
            enableLowInitialPlaylist: true,
            smoothQualityChange: true,
          },
        },
      });

      // 設定事件監聽器
      this.player.on('error', this.handleError);
      this.player.on('loadstart', () => {
        this.isLoading = true;
        this.error = null;
      });
      this.player.on('canplay', () => {
        this.isLoading = false;
      });
    },

    loadStream() {
      if (!this.streamUrl.trim()) {
        this.error = '請輸入 URL';
        return;
      }

      if (!this.player) {
        this.error = '播放器未初始化';
        return;
      }

      try {
        this.player.src({
          src: this.streamUrl,
          type: 'application/x-mpegURL',
        });
      } catch (err) {
        this.handleError(err);
      }
    },

    handleError(error) {
      console.error('播放器錯誤:', error);
      this.error = '串流載入錯誤';
      this.isLoading = false;
    },
  },
};
</script>

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

.controls {
  margin-bottom: 20px;
}

.controls input {
  width: 70%;
  padding: 8px;
  margin-right: 10px;
}

.controls button {
  padding: 8px 16px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.controls button:disabled {
  background: #ccc;
  cursor: not-allowed;
}

.error-message {
  color: red;
  background: #ffe6e6;
  padding: 10px;
  border-radius: 4px;
  margin-bottom: 20px;
}

.loading-message {
  color: #007bff;
  text-align: center;
  padding: 20px;
}

.player-container {
  background: #000;
  border-radius: 8px;
  overflow: hidden;
}
</style>

這些範例程式碼讓您能夠在各種環境和框架中實作 M3U8 線上播放器。

這個頁面對您有幫助嗎?