iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
Modern Web

新手進化日記,從React至Redux Saga系列 第 21

Day 21 - YouTube Iframe API: 打造全面的YT控制器component

  • 分享至 

  • xImage
  •  
tags: iThome 鐵人賽 30天

今天要來打造影片標記系統的精隨之一,用YouTube Iframe API來建立一個播放器。或許有人會有疑問,為甚麼不用Iframe就好了呢?

因為為了能夠隨時抓取YouTube影片的撥放時間、打字編輯標記時也能夠控制影片撥放暫停等,所以使用YouTube Iframe API輔助來打造高控制度的撥放器。

功能介紹

這裡大致介紹一下功能,主要介紹一些function與event listener。

funtion:

function 說明
playVideo 播放
pauseVideo 暫停
stopVideo 停止
seekTo 跳到影片位置 (秒)
mute 靜音
unMute 解除靜音
isMuted 查看現在是否靜音
setVolume 設定音量
getVolume 取得目前音量
getCurrentTime 取得現在影片撥放時間點
getDuration 取得影片長度

事件:

事件名稱 說明
onReady 當影片準備好時
onStateChange 當影片狀態改變時 (-1 尚未開始、0 結束、1 正在播放、2 暫停、3 緩衝、5 影片提示)
onPlaybackQualityChange 當影片畫質改變時
onPlaybackRateChange 當影片播放速度改變時
onError 當影片發生錯誤時
onApiChange 當YouTube API改變時

以上是常用到的function與event,如果想查看更詳細的API的話可以到官方API文件去查詢喔!

YouTube Iframe API:使用方法

第一個步驟先定義要放置player的容器 (id可以自己取名):

<div id="player"></div>

再來就是要先把Iframe的API載入進來 (當api準備好時會執行loadVideo):

const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

定義loadVideo來撥放影片:

loadVideo = () => { // the Player object is created uniquely based on the id in props
const { videoid } = this.props;
player = new YT.Player(playerid, {
    videoId: videoid,
    playerVars: {
        'playsinline': 1
      },
    events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange,
        'onPlaybackQualityChange': onPlaybackQualityChange,
    },
})

YouTube Iframe API component化,讓使用上更加方便

介紹使用方法後就是來把這些步驟包成一個component來處理啦~

component第一次執行載入js:

useEffect(() => {
  loadYTApi()
}, [])

const loadYTApi = () => {
  if (!window.YT) { // If not, load the script asynchronously
    const tag = document.createElement('script');
    tag.src = 'https://www.youtube.com/iframe_api';
    window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  } else { // If script is already there, load the video directly
    this.loadVideo();
  }
}

讓影片 (v) 與開始時間 (t) 由props傳入,並請使用者傳入設定player與其他監聽事件的function:

const { v, t, setPlayer, onPlayerReady, onPlayerStateChange, onPlaybackQualityChange, player, playerid } = props

const loadVideo = () => { // the Player object is created uniquely based on the id in props
    setPlayer(new window.YT.Player(playerid, {
        videoId: v,
        playerVars: {
            'start': parseFloat(t)
        },
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange,
            'onPlaybackQualityChange': onPlaybackQualityChange
        },
    }))
};

當傳入的vt變動時,更新影片或時間點:

const { v, t, setPlayer } = props

// 當影片id變更時,載入新的影片
useEffect(() => {
    player &&
        player.loadVideoById({
          videoId: v,
          startSeconds: parseFloat(t)
        })
}, [v])

// 開始時間改變時,跳轉到該時間
useEffect(() => {
    player &&
        player.seekTo(t)
}, [t])

把上面講述到的融合後,得到的component:

import React, { useEffect } from 'react'

export default function YouTubeIframe(props) {
  const { v, t, setPlayer, onPlayerReady, onPlayerStateChange, onPlaybackQualityChange, playerid, player } = props

  useEffect(() => {
    loadYTApi()
  }, [])

  // 當影片id變更時,載入新的影片
  useEffect(() => {
    !!player &&
      player.loadVideoById({
        videoId: v,
        startSeconds: parseFloat(t)
      })
  }, [v])

  // 開始時間改變時,跳轉到該時間
  useEffect(() => {

    !!player &&
      player.seekTo(t)
  }, [t])

  const loadYTApi = () => {
    if (!window.YT) { // If not, load the script asynchronously
      const tag = document.createElement('script');
      tag.src = 'https://www.youtube.com/iframe_api';
      window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
      const firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    } else { // If script is already there, load the video directly
      loadVideo();
    }
  }

  const loadVideo = () => { // the Player object is created uniquely based on the id in props
    setPlayer(new window.YT.Player(playerid, {
      videoId: v,
      playerVars: {
        'start': parseFloat(t)
      },
      events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange,
        'onPlaybackQualityChange': onPlaybackQualityChange
      },
    }))
  };

  return (
    <></>
  )
}

後記

明天就來試用看看並調整今天做的component吧!

看完今天Google的發表會,雖然又有些新的技術與新出來的手錶,雖然手錶真的很美,但感覺這次發表會沒有一種吸引人想買的衝動,硬體上面又感覺力不從心,只好等等看明年吧... (Pixel 6的苦主在這

附上專案:2022-iThomeIronman

對資安或Mapbox有興趣的話也可以觀看我們團隊的鐵人發文喔~


上一篇
Day 20 - Container Wrapper包裝影片卡片 / Array Mapping
下一篇
Day 22 - YouTube Iframe API實際應用 / react-router-dom v6
系列文
新手進化日記,從React至Redux Saga30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言