팡이네

* 2024-06-17 현재 video.js 8이상 버전에서는

vhs(video.js Http Streaming) 메소드에 오류가 있어

7.21.6 버전으로 설정하는 방법을 기록

* 8 버전의 경우 player.tech().vhs.xhr ... 여기서 vhs 객체가 없다고 오류 발생

 

angular.json

...
        "scripts": [
            "./node_modules/video.js/dist/video.min.js",
            ...
        ],
...

 

styles.scss

@import '../node_modules/video.js/dist/video-js.min.css';

 

video.component.html

vjs-big-play-centered 최초 재생 버튼을 플레이어 한가운데 표시

<video #video id="video" class="video-js vjs-big-play-centered"></video>

 

video.component .ts

import videojs from 'video.js';
// json 파일을 import 하기 위해서는
// tsconfig.json 파일에서 resolveJsonModule: true 옵션 사용
import ko from 'video.js/dist/lang/ko.json';
...
@Input() fileID?: string;
@Input() width?: string;
@Input() height?: string;
@Input() token?: string;
@ViewChild('video', { static: true }) target?: ElementRef;
...

ngAfterViewInit(): void {
    // 한글 언어 파일 설정
    videojs.addLanguage('ko', ko);
    setTimeout(() => {
        this.readyVideo();
    });
}

ngOnDestroy(): void {
    if (this.videoPlayer) {
        this.videoPlayer.dispose();
    }
}

readyVideo(): void {
    // video.js (v7.21.6)
    // header에 스트리밍을 위한 인증 토큰 추가
    videojs.Vhs.xhr.beforeRequest = (options: any) => {
        options.headers = {
            Authorization: 'Bearer '+ this.token
        };
        return options;
    };

    // video.js 옵션 (v7.21.6)
    const options = {
        controls: true,     // 조작 컨트롤 표시
        controlBar: {
            bigPlayButton: true,            // 최초 재생 버튼 표시
            playToggle: true,               // 재생/일시정지 버튼 표시
            volumePanel: {                  // 소리 버튼 표시
                inline: false,              // 음량 조절막대 위치(true: 진행률막대 왼쪽에 표시, false: 세로로 표시)
            },
            progressControl: true,          // 진행률막대 표시
            // currentTimeDisplay: false,      // 재생 시간 표시
            remainingTimeDisplay: true,     // 남은 시간 표시
            pictureInPictureToggle: false,  // PiP 버튼 표시
            // fullscreenToggle: false,     // 전체화면 버튼 표시
        },
        muted: true,        // 소리 꺼짐여부
        autoplay: false,    // false, true, 'muted', 'play', 'any'
        preload: 'none',    // 'auto', 'metadata', 'none'
        playsinline: true,  // 내장 플레이어 재생여부(모바일)
        playbackRates: [0.5, 1, 1.5, 2],    // 재생속도 설정
        width: this.width,
        height: this.height,
        sources: [{
            type: 'application/x-mpegURL',
            src: '/스트리밍파일경로/파일명.m3u8',
        }],
        languages: {    // 번역 수정
            'ko': {
                'Mute': '소리 끄기',    // 기존번역: 음소거
                'Unmute': '소리 켜기',  // 기존번역: 소리 활성화하기
                'Picture-in-Picture': 'PiP 모드', // 미번역
            },
        },
        poster: '/포스터이미지파일경로/파일명.png',	// 1. 직접 이미지 표시할 경우
    };

    // 1. 직접 이미지 표시할 경우
    this.videoPlayer = videojs(this.target?.nativeElement, options);

    // 2. base64 문자열로 이미지를 표시할 경우 위의 1. 부분을 삭제
    this.imageService.getImage(this.fileID)
        .subscribe((blob) => {
            this.loadImage(blob);
            this.videoPlayer = videojs(this.target?.nativeElement, options);
        });
}

/**
 * Blob 데이터 로딩
 * @param img
 */
private loadImage(img: Blob): void {
    const reader = new FileReader();
    reader.addEventListener('load', () => {
        this.videoPlayer.poster(reader.result);
    }, false);

    if (img) {
        reader.readAsDataURL(img);
    }
}