w

샘플 코드

M3U8 온라인 플레이어의 구현 예제와 샘플 코드입니다.

기본 HTML/JavaScript 예제

간단한 플레이어

<!DOCTYPE html>
<html lang="ko">
  <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="ko">
  <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 온라인 플레이어를 다양한 환경에서 구현할 수 있습니다.

이 페이지가 도움이 되었나요?