Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ReactHooksでvideoを乗りこなす

narirou
November 02, 2019

 ReactHooksでvideoを乗りこなす

「映像プレーヤー」を作成したことはあるでしょうか?
作成したことはなくても、おそらく日々様々なプレーヤーを利用していると思います。
HTML5で定義されるHTML Video Elementからは、メディア状態・エラー・DRM複合処理・広告再生・読み込み、などブラウザごとに多種多様なイベントが発生します。プレーヤーはこのイベントに加え、ユーザーから入力される複雑な操作も併せて処理する必要があります。UIや内部要件を実装するには、このイベント制御を適切にハンドルし、のりこなすことが必要不可欠です。
今回のセッションでは、複雑な処理をReactHooksを用いてHTML Video elementを制御する手法を、現在映像サービス「GYAO!」でプロダクションで使用されているプレーヤーを事例に、フロントエンドの観点からご紹介します。

narirou

November 02, 2019
Tweet

More Decks by narirou

Other Decks in Programming

Transcript

  1. React HooksͰ Λ৐Γ͜ͳ͢
    2019-11-2
    ϑϩϯτΤϯυΧϯϑΝϨϯε2019
    Yahoo! JAPAN / GYAO!

    View Slide

  2. MASANARI HAMADA
    ͳΓΖ͏ @narirow
    ͓࢓ࣄ
    Yahoo! JAPAN 2015೥৽ଔೖࣾ
    GYAO! WebͷϑϩϯτΤϯυΞʔΩςΫνϟ࡮৽
    GYAO! ಈըϓϨʔϠʔ࡮৽
    GYAO! iOS SwiftԽ / ϦϑΝΫλϦϯά౳
    σβΠϯγεςϜ / JAMStack / WEBඪ४
    ౳ʹڵຯ͕͋Γ·͢
    ࣥච
    WEB+DB PRESS ͷϑϩϯτΤϯυ࿈ࡌهࣄͱͯ͠Storybookɺ
    HeadlessCMSͷهࣄΛࣥච

    View Slide

  3. APENDIX
    ಈըϓϨʔϠʔʁ ಛ௃ͱෳࡶੑʹ͍ͭͯ
    Videoͷෳࡶͳঢ়ଶͷ੍ޚΛReactHooksΛ༻͍ͯߦ͏
    ReactHooksͰϓϨʔϠʔͷUIΛ࡞Γ͜Ή

    View Slide

  4. օ͞Μ͸ WEB Ͱ ಈը Λ؍Δ
    ػձ͸͋Γ·͔͢ʁ
    ੈͷதʹ͸ͨ͘͞ΜͷVODαʔϏε͕͋Γ·͢

    View Slide

  5. ಈըϓϨʔϠʔͷੈք΁
    ಈըίϯςϯπͷັྗΛ࠷େݶҾ͖ग़ͨ͢Ίʹ͸ɺ
    շదʹ࠶ੜ͠շదʹૢ࡞͕Ͱ͖ΔϓϨʔϠʔ͕ෆՄܽ

    View Slide

  6. ϓϨʔϠʔͷ಺෦ͰԿ͕ى͖͍ͯΔ͔ɺͦͷ՝୊Λ஌Ζ͏
    ಈըϓϨʔϠʔͷੈք΁

    View Slide

  7. 1. ಈըϓϨʔϠʔͷෳࡶੑ

    View Slide

  8. WEB͸Ͳ͏΍ͬͯಈըΛ࠶ੜ͢Δʁ
    HTMLVideoElement
    HTMLVideoElement͸ɺHTMLMediaElementΛܧঝͨ͠WEBͰಈը
    ϑΝΠϧΛऔΓѻ͏ͨΊͷHTMLཁૉɻ
    ಈըʹؔ͢ΔΠϯλϥΫςΟϒͳॲཧΛߦ͏͜ͱ͕Ͱ͖·͢ɻ

    View Slide


  9. type="video/webm">
    type="video/mp4">
    ͋ͳͨͷϒϥ΢β͸ಈը࠶ੜͰ͖·ͤΜ

    ୯७ͳ࠶ੜ͸ͨͬͨ͜Ε͚ͩɻϒϥ΢β͸ݡͯ͘ɺΦϓγϣϯͷtype
    ଐੑ͔ΒϑΝΠϧΛμ΢ϯϩʔυͯ͠࠶ੜ͢Δ͔Λ൑அ͠·͢ɻ
    ଐੑʹΑͬͯॳظ஋΍ಈ࡞ΛࢦఆͰ͖ɺΠϯλʔϑΣʔε͔ΒԻྔɺ
    όοϑΝϦϯάɺ࠶ੜҐஔͳͲΛ੍ޚͰ͖·͢ɻ

    View Slide

  10. ඪ४ͷ PlayerUI ͸ɺ֤ϒϥ΢βͰσβΠϯ΍ػೳʹ͕ࠩ͋Γ·͢ɻ
    Safari Google Chrome
    Ͱɺ֤ϒϥ΢βͷඪ४ίϯτϩʔϧUIදࣔ
    Ͱ͖·͢ɻ

    View Slide

  11. ͦͷͨΊɺҰൠతʹϓϨʔϠʔΛ࡞੒͢Δʹ͸ɺ
    ೚ҙͷUIΛ্͔Β෴͍ඃͤͯػೳΛ௥Ճ͠ɺΫϩεϒϥ΢βͰಈ࡞
    ͢Δݻ༗ͷϓϨʔϠʔΛ࡞੒͍ͯ͘͜͠ͱʹͳΓ·͢ɻ
    ΧελϜUI

    View Slide

  12. ࠶ੜʹؔ܎͢Δٕज़
    ετϦʔϛϯά࠶ੜ
    MediaSourceExtension
    HLS (HTTP Live Streaming)
    MPEG-DASH
    ίϯςϯπอޢ
    Encrypted Media Extensions
    DRMϥΠηϯε
    ࣈນ
    WEB VTT
    ಈը޿ࠂ
    VAST / VMAP
    (IAB)
    ࠶ੜʹؔ࿈͢Δٕज़͸ɺ௿ϨΠϠʔΛؚΈɺଟ༷ͳ෼໺ʹΘͨΓ·͢ɻ

    View Slide

  13. ࠶ੜͷΠϕϯτ (HTML Media Events)
    ❖ loadedmetadata….
    ❖ loadeddata………..
    ❖ canplay…………….
    ❖ canplaythrough…..
    ❖ waiting…………….
    ❖ etc….
    ಈըͷϝλσʔλ͕ಡΈࠐ·Εͨ࣌
    ಈըͷॳظϑϨʔϜ͕ಡΈࠐ·Εͨ࣌
    ࠶ੜ͕Մೳͱͳͬͨ࣌
    όοϑΝϦϯάΛߟྀͯ͠࠶ੜ͕Մೳͱͳͬͨ࣌
    ࠶ੜʹඞཁͳσʔλ͕ෆ଍͠࠶ੜ͕ఀࢭͨ࣌͠
    ͜ΕΒͷٕज़Λ΋ͬͯ಺෦ͰߦΘΕͨॲཧ͸ɺө૾࠶ੜʹؔ͢ΔΠϕ
    ϯτͱͯ͠ɺදࣔॲཧܥʹ௨஌͞Ε·͢ɻ

    View Slide

  14. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Ϣʔβʔૢ࡞
    Video Element Player UI
    ө૾ͷঢ়ଶΛ൓ө
    ಈըͷঢ়ଶͷ͔ͨ·Γ ෳࡶͳUIૢ࡞

    View Slide

  15. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Ϣʔβʔૢ࡞ͷঢ়ଶ
    Video Element Player UI
    ө૾ͷঢ়ଶΛ൓ө
    ϓϨʔϠʔ͸ʮঢ়ଶʯͱ
    ෳࡶͳʮϢʔβʔૢ࡞ʯͷަࠩ఺

    View Slide

  16. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Ϣʔβʔૢ࡞ͷঢ়ଶ
    Video Element Player UI
    ө૾ͷঢ়ଶΛ൓ө
    REACT

    View Slide

  17. ͜ͷෳࡶੑΛREACTʹͲ͏
    ൓ө͍͔ͯ͘͠

    View Slide

  18. 11/1 GYAO!ετΞ ϦχϡʔΞϧ
    https://gyao.yahoo.co.jp/store

    View Slide

  19. ͦͷࡍʹɺϓϨʔϠʔΛ
    ReactHooksΛ࢖༻ͨ͠ΞʔΩςΫνϟͰ࣮૷ɻ
    ❖ preact 10.0 (hooks)
    ❖ dash.js with DRM

    View Slide

  20. 2. ͷঢ়ଶ؅ཧ

    View Slide

  21. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Ϣʔβʔૢ࡞ͷঢ়ଶ
    ө૾ͷঢ়ଶΛ൓ө
    લड़ͨ͠Α͏ʹɺϓϨʔϠʔʹ͸ओʹɺө૾ͷঢ়ଶมԽͱɺϢʔβʔ
    ૢ࡞ʹؔ͢Δঢ়ଶมԽͷओʹ2ͭΛ੍ޚ͠·͢ɻ
    Player UI
    Video Element
    ಈըͷঢ়ଶͷ͔ͨ·Γ ෳࡶͳUIૢ࡞

    View Slide

  22. ❌ ͜Ε·Ͱͷෛ࠴
    ҎલͷPlayerͰ͸ɺ֤ཁૉͷίϯϙʔωϯτ͝ͱʹΠϕϯτΛϋϯυ
    ϧ͠ɺݸผʹ੾Γସ͑Δ͜ͱΛߦ͍ͬͯ·ͨ͠ɻ
    ө૾ঢ়ଶ (currentTime)
    timeupdateΠϕϯτ
    Player UI
    Video Element
    γʔΫόʔ

    View Slide

  23. ͜ͷख๏͸ཁྖ͕ѱ͘ɺεέʔϧ͠·ͤΜͰͨ͠ɻ
    ෼ׂ͸Ͱ͖͍ͯΔ΋ͷͷɺvideoͷΠϕϯτۦಈͷঢ়ଶมԽΛݸผʹه
    ड़͓ͯ͠Γɺඞཁͳॲཧ͕ࢄΒ͹ͬͯɺຊདྷͷίϯϙʔωϯτ͕ߦ͍
    ͍ͨಈ࡞Λ௥͏͜ͱ͕ࠔ೉ͱͳͬͨͷͰ͢ɻ
    Video Element

    View Slide

  24. React Hooks

    View Slide

  25. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Video Element ঢ়ଶ (STORE)
    มߋ௨஌ (ACTION)
    Player UI
    ࠓճ͸ɺReactͰө૾(video)ͷঢ়ଶΛద੾ʹѻ͏ͨΊʹɺVideo
    ElementΛঢ়ଶͱΞΫγϣϯʹ෼཭͠ɺͦͷ৘ใͷΈࢀর͢ΔΑ͏ʹม
    ߋ͠·ͨ͠ɻ

    View Slide

  26. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Video Element ঢ়ଶ (STORE)
    มߋ௨஌ (ACTION)
    Player UI
    ͜ͷ࣌ɺSTORE(useContext)ͱͯ͠ѻ͏ঢ়ଶʹ͸ɺUIʹඞཁ
    ࠷খݶͳ஋ʹߜΓ·͢ɻ

    View Slide

  27. ෳ਺ͷΠϕϯτΛ߹੒ͯ͠࠷ऴతʹҰͭͷ஋Λར༻͢Δ৔߹ɺͦͷ໾ׂ
    ಺Ͱ͋Ε͹ɺ͜ͷதͰٵऩ͠·͢ɻ
    ReactHooksͰߦ͏৔߹ɺҎԼͷΑ͏ʹͳΓ·͢ɻ
    ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Video Element ঢ়ଶ (STORE)
    มߋ௨஌ (ACTION)
    Player UI

    View Slide

  28. // ࠶ੜऴྃ
    useEffect(() => {
    const onEnded = () => {
    setEnded(true);
    };
    player.on('ended', onEnded);
    return () => {
    player.off('ended', onEnded);
    };
    }, [player]);
    // ϦϓϨΠ
    useEffect(() => {
    if (!ended) return () => {};
    const onReplay = () => {
    setSessionId(generateSessionId());
    setEnded(false);
    player.off('playing', onReplay);
    };
    player.on('playing', onReplay);
    return () => {
    player.off('playing', onReplay);
    };
    }, [ended, player]);

    View Slide

  29. 3. ContextͱύϑΥʔϚϯε

    View Slide

  30. ࠶ੜٕज़
    ө૾ঢ়ଶ
    ෳ਺ͷ࠶ੜΠϕϯτ
    Video Element ঢ়ଶ (STORE)
    มߋ௨஌ (ACTION)
    Player UI
    ࠶ϨϯμϦϯά
    ࠶ϨϯμϦϯά
    ࠶ϨϯμϦϯά

    Context͸ศརͰ͕͢ɺ͢΂ͯͷ৘ใΛ΋ͨͤͯ͠·͏ͱɺ
    ߋ৽ස౓ͷߴ͍ঢ়ଶʹҾ͖ͮΒΕɺProvidor഑ԼͷίϯϙʔωϯτͰ࠶
    ϨϯμϦϯά͕ൃੜ͠·͢ɻ

    View Slide

  31. interface InitialState = {
    currentTime: number //- ݱࡏͷ࠶ੜҐஔ(࣌ؒ)
    media: Media //- ө૾ͷϝλ৘ใ
    }
    ݱࡏͷ࣌ؒ͸ө૾͕࠶ੜ͞ΕΔͨͼʹසൟͳߋ৽͕૸Γ·͕͢ɺ
    ө૾ͷϝλσʔλ͸ॳճͷΈσʔλΛऔಘͯ͠ɺ࠶ಡΈࠐΈ͕૸
    Βͳ͍ݶΓ͸มԽ͠ͳ͍஋Ͱ͢ɻ
    ྫ͑͹ɺҎԼͷΑ͏ͳঢ়ଶΛ΋͍ͬͯͨ৔߹

    View Slide

  32. 1: ContextΛػೳυϝΠϯ͝ͱʹ෼ׂ͢Δ
    ߋ৽ස౓΍໨తͷҟͳΔঢ়ଶΛɺಉ͡Contextʹ͸ೖΕͳ͍Α͏ʹ͠
    ·͢ɻ
    ϓϨʔϠʔͷContextΛߋ৽ස౓ͱར༻༻్͝ͱʹΘ͚ɺޓ͍ʹӨڹ
    ͠ͳ͍Α͏ʹ͢Δ͜ͱ͕ॏཁͰ͢ɻ

    View Slide

  33. ঢ়ଶ (STORE)
    MediaContext (ө૾ϝλ)
    media
    diration
    PlaybackContext (ө૾ͷ࠶ੜ৘ใ)
    currentTime,
    paused,
    ended,
    seeking,
    waiting,
    buffer
    PlaybackSettingContext (ө૾ͷ࠶ੜ৘ใ)
    muted,
    playbackRate,
    videoQuality,
    isFullscreen
    ߋ৽ස౓ ߴ
    ߋ৽ස౓ ௿
    ෼ղ

    View Slide

  34. MediaContext (ө૾ϝλ)
    PlaybackContext (ө૾ͷ࠶ੜ৘ใ)
    PlaybackSettingContext (ө૾ͷ࠶ੜ৘ใ)
    ߋ৽ස౓ ߴ
    ߋ৽ස౓ ௿
    ස౓ͱ໾ׂ͝ͱʹ෼ׂͨ͜͠ͱʹΑΓɺ
    ContextʹΑΔ࠶ϨϯμϦϯάίετΛ཈͑Δɻ
    γʔΫόʔɺ࣌ؒදࣔɺόοϑΝදࣔ
    λΠτϧදࣔɺαϜωΠϧ
    ઃఆ

    View Slide

  35. 2: useMemoΛڬΉ
    ෛՙͷߴ͍ίϯϙʔωϯτʹͷΈඞཁͳ৘ใΛ౉͠ϝϞԽ͠·͢ɻ
    ਌ͷϨϯμϦϯά͸࣮ߦ͞Ε·͕͢ɺuseMemoͷೖྗ͕ಉ͡৔߹
    ʹɺࢠίϯϙʔωϯτ͸ϨϯμϦϯά͞Εͳ͘ͳΓ·͢ɻ
    function MediaDitails () {
    const { media } = useContext(PlayerContext);
    return
    }
    const MediaInfomation = useMemo(media => {
    // ԼهʹϨϯμϦϯάίετͷߴ͍ॲཧΛهड़
    return ;
    }, [media]);

    View Slide

  36. 4. ϫʔΫΞϥ΢ϯυͷ੍ޚ

    View Slide

  37. σόΠε΍ϒϥ΢βʹࠩҟ͕ൃੜ͢Δॲཧ͸ɺঢ়ଶΛ࡞Γग़͢
    ࣌΍ɺந৅Խͨ͠ϨΠϠʔΛڬΉ͜ͱͰٵऩ͠ɺ֎ଆͷUI෦෼
    Ͱҙࣝ͠ͳ͍ߏ଄Λͭ͘Γ·͢ɻ
    UIଆ͕ʮ༨ܭͳ͜ͱʯΛҙࣝ͠ͳ͍Α͏ʹ
    Playerͷ಺෦Ͱ͸͜͏ͨ͠ॲཧ͕ଟ͘ൃੜ͢ΔͨΊ
    ౎౓ରԠ͢Δͱഁ୼͢Δ

    View Slide

  38. D
    document.addEventListener('fullscreenchange', handler);
    document.addEventListener('webkitfullscreenchange', handler);
    document.addEventListener('mozfullscreenchange', handler);
    document.addEventListener('MSFullscreenChange', handler);
    UIଆ͕ʮ༨ܭͳ͜ͱʯΛҙࣝ͠ͳ͍Α͏ʹ
    ྫ͑͹fullscreenΛߦ͏ॲཧͷ৔߹… શ෦ҧ͏!
    import { requestVideoFullscreen, exitVideoFullscreen } from './util/video-fullscreen';

    View Slide

  39. ྫ͑͹ɺGoogle͕࡞੒͢ΔShakaPlayerͰ͸ɺଟ͘ͷॲཧΛಠࣗ
    ͷPollyfillΛ௨͢͜ͱͰந৅Խ͠ɺར༻ଆͰ͸ͦͷࠩΛҙࣝ͠ͳ͍
    Α͏ʹهड़͍ͯ͠·͢ɻࢲୡ΋͜ͷ࣮૷Λࢀߟʹ͠·ͨ͠ɻ
    https://github.com/google/shaka-player/tree/master/lib/polyfill
    (ྫ) EMEͷMediaKeysΛऔಘ͢Δॲཧ

    View Slide

  40. 5. ϓϨʔϠʔͷʮࣗવͳಈ࡞ʯΛ࡞Δ

    View Slide

  41. ྫ͑͹ɺϓϨʔϠʔʹ͸ίϯτϩʔϧόʔ͕දࣔ͞Ε·͢
    ͕ɺਓ͕ؒࣗવͱײ͡Δදࣔͷಈ͖Λ࡞ΔͨΊʹ͸ɺ࣍ͷΑ
    ͏ͳ੍ޚ͕ඞཁͰ͢ɻ
    ίϯτϩʔϧόʔ

    View Slide

  42. ❖ Ϣʔβʔ͕Կ͔ૢ࡞͍ͯ͠Δ࣌͸දࣔ͢Δ
    ❖ Ϣʔβʔ͕ૢ࡞Λऴྃͨ͠ͱ͖ʹ͸ফ͑Δ
    ❖ ॳճ͸਺ඵදࣔ͢Δ
    (ॳճʹද͓͔ࣔͯ͠ͳ͍ͱͲΜͳૢ࡞͕Ͱ͖Δ͔ೝࣝͰ͖ͳ͍ͨΊ)
    ❖ ಈը͕ఀࢭͨ͠ͱ͖͸දࣔ͢Δ
    (Ұ࣌ఀࢭ͍ͯ͠Δ͜ͱΛ఻͑ΔͨΊ)
    ୯७ͳϗόʔͷΈͰ͸ɺࣗવͳಈ࡞ʹͳΒͳ͍

    View Slide

  43. ࣮ࡍʹಈ࡞ΛݟͯΈΔ

    View Slide

  44. ❖ Ϣʔβʔ͕Կ͔ૢ࡞͍ͯ͠Δ࣌͸දࣔ͢Δ

    View Slide

  45. ❖ ૢ࡞͕ऴྃͯ͠ɺಈըʹूத࢝͠ΊͨΒফ͑Δ

    View Slide

  46. ❖ Ϛ΢ε͕ը໘಺֎ʹདྷͨͱ͖͸ଈ࠲ʹදࣔɾඇදࣔ

    View Slide

  47. ❖ ॳճ͸͠͹Β͘ද͓ࣔͯ͘͠

    View Slide

  48. ঢ়ଶͷ߹੒ΛReactͰͲͷΑ͏ʹѻ͏͔
    1ͭͷؔ৺͝ͱʹΧελϜϑοΫΛ࢖༻ͯ͠෼཭͢Δ
    ྫ͑͹্هͷΑ͏ͳɺ
    ❖ ॳճද͍ࣔͯ͠Δ͔
    ❖ Ϣʔβʔ͕ཁૉʹؔ৺Λ΋͍ͬͯΔ͔
    Λ1୯Ґͱͯ͠ΧελϜϑοΫΛ࡞੒͠·͢ɻ

    View Slide

  49. export const useUserAttention = attentionDistractedIntervalMillis => {
    // - ΠϕϯτϦεφͷొ࿥(লུ)…
    const userAttention = useMemo(() => (isMouseHovered && isMouseMoving) ||
    isTouchFocused, []);
    return {
    userAttention,
    containerElementRef,
    };
    };
    useUserAttension
    Ϣʔβʔ͕ϓϨʔϠʔʹ஫ҙΛҾ͍͍ͯΔ͔

    View Slide

  50. export const ControlContainer = () => {
    const { seeking, paused } = useContext(PlaybackContext);
    const { userAttention, initialAttention } = useContext(PlayerFrameContext);
    const className = classNames('gyp-control-container', {
    'is-active': userAttention || initialAttention || seeking || paused,
    });
    return h`

    ...

    `;
    };
    ControlContainer (ίϯτϩʔϧόʔཁૉ)
    ෼ׂ͞ΕͨContextܦ༝ͰΧελϜϑοΫ͔Βͷ஋Λऔಘ
    ෳࡶͳॲཧͰ͕͢ɺͳͥίϯτϩʔϧόʔ͕දࣔ͞ΕΔ͔
    ໌ࣔతʹهड़Ͱ͖Δ

    View Slide

  51. 6. ·ͱΊ

    View Slide

  52. ө૾ϓϨʔϠʔͷಛ௃ͱɺෳࡶੑʹ͍ͭͯ
    Video ͷෳࡶͳঢ়ଶΛ ReactHooks Ͱ੍ޚ͢Δ
    hooksͷػߏ͸ɺ͜Ε·ͰϥΠϑαΠΫϧͷ੍໿ʹ્·Εͯࢄࡏ͍ͯͨ͠ঢ়ଶ
    ͷ؅ཧΛγϯϓϧʹ͢Δ
    ස౓ͱ໾ׂʹԠͯ͡ContextΛ෼ׂ؅ཧ͢Δ
    ReactHooksͰϓϨʔϠʔͷUIΛ࡞Γ͜Ή
    ϢʔβʔͷҰ࿈ͷૢ࡞ΛΧελϜϑοΫͰ·ͱΊͯ׆༻͢Δ

    View Slide

  53. ΑΓਂ͘஌Δʹ͸ (ࢀߟจݙ)
    The Video Embed element
    https://developer.mozilla.org/ja/docs/Web/HTML/Element/video
    ಈը | Web | Google Developers
    https://developers.google.com/web/fundamentals/media/video?hl=ja
    Preventing rerenders with React.memo and useContext hook
    https://github.com/facebook/react/issues/15156
    Building Your Own Hooks
    https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook
    HTML Media Events
    https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events

    View Slide