From 48170b487c512ed5fb0786ea992eeb545cd369ac Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Fri, 27 Nov 2020 12:17:32 +0100
Subject: [PATCH] Provide feedback for audio/sub selection

---
 src/routes/player/Player.tsx                 | 25 ++++++++---
 src/routes/player/subtitles/TtmlRenderer.tsx | 16 ++++---
 src/routes/player/video/DashVideoElement.tsx | 25 +++++++----
 src/routes/player/video/RawVideoElement.tsx  |  7 ++-
 src/routes/player/video/VideoApi.ts          |  4 +-
 src/util/media/useAudioTracks.ts             | 45 ++++++++++++--------
 6 files changed, 81 insertions(+), 41 deletions(-)

diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx
index c812b3e..2e9fd7d 100644
--- a/src/routes/player/Player.tsx
+++ b/src/routes/player/Player.tsx
@@ -40,7 +40,7 @@ export function Player(
 
     const position = usePosition(videoElement);
     const duration = useDuration(videoElement);
-    const audioTracks = useAudioTracks(videoElement);
+    const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(videoElement);
 
     useDebugInfo("playerEl", videoElement);
     useDebugInfo("previewTrackEl", previewTrack);
@@ -87,6 +87,7 @@ export function Player(
                     <TtmlRenderer
                         className={classes.subtitleCanvas}
                         trackElement={subtitleTrack}
+                        subtitle={subtitle}
                         duration={duration}
                     />
                 </div>
@@ -101,18 +102,28 @@ export function Player(
                         <strong>{track.lang}</strong>
                         &nbsp;{track.labels}
                         &nbsp;
-                        <button onClick={() => videoElement?.setAudioTrack(track)}>Choose</button>
+                        <button
+                            disabled={currentTrack === track}
+                            onClick={() => setCurrentTrack(track)}
+                        >
+                            Choose
+                        </button>
                     </li>
                 ))}
             </ul>
             <h3>Subtitles</h3>
             <ul>
-                {content.subtitles.map(track => (
-                    <li key={track.src}>
-                        <strong>{track.language}</strong>
-                        &nbsp;{track.specifier}
+                {[null, ...content.subtitles].map(track => (
+                    <li key={track?.src || "none"}>
+                        <strong>{track?.language || "none"}</strong>
+                        &nbsp;{track?.specifier}
                         &nbsp;
-                        <button onClick={() => setSubtitle(track)}>Choose</button>
+                        <button
+                            disabled={subtitle === track}
+                            onClick={() => setSubtitle(track)}
+                        >
+                            Choose
+                        </button>
                     </li>
                 ))}
             </ul>
diff --git a/src/routes/player/subtitles/TtmlRenderer.tsx b/src/routes/player/subtitles/TtmlRenderer.tsx
index bd599e6..06c2112 100644
--- a/src/routes/player/subtitles/TtmlRenderer.tsx
+++ b/src/routes/player/subtitles/TtmlRenderer.tsx
@@ -1,24 +1,30 @@
-import { useEffect, useState } from "react";
+import {useEffect, useState} from "react";
 import TtmlHelper from "./TtmlHelper";
+import {Subtitle} from "../../../api/models/Subtitle";
 
 interface Props {
     trackElement: HTMLTrackElement | null,
+    subtitle: Subtitle | null,
     duration: number,
     className?: string,
 }
 
 export function TtmlRenderer(
-    {trackElement, duration, className}: Props
+    {trackElement, subtitle, duration, className}: Props
 ) {
     const [subtitleCanvas, setSubtitleCanvas] = useState<HTMLElement | null>(null);
 
     useEffect(() => {
-        if (subtitleCanvas && trackElement) {
+        if (subtitleCanvas && trackElement && subtitle) {
             return TtmlHelper.bindToTrack(trackElement, subtitleCanvas, duration)
         }
-    }, [subtitleCanvas, trackElement, duration]);
+    }, [subtitleCanvas, subtitle, trackElement, duration]);
 
     return (
-        <div ref={setSubtitleCanvas} className={className}/>
+        <div
+            ref={setSubtitleCanvas}
+            lang={subtitle?.language || undefined}
+            className={className}
+        />
     )
 }
diff --git a/src/routes/player/video/DashVideoElement.tsx b/src/routes/player/video/DashVideoElement.tsx
index c6698ea..bca35e0 100644
--- a/src/routes/player/video/DashVideoElement.tsx
+++ b/src/routes/player/video/DashVideoElement.tsx
@@ -73,7 +73,22 @@ export const DashVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(f
             }
             return !Number.isNaN(player.duration());
         },
-        setAudioTrack(track: MediaInfo) {
+        getAudioTracks(): MediaInfo[] {
+            if (!player.isReady()) {
+                return [];
+            }
+            return player.getTracksFor("audio").map((info: MediaInfo) => {
+                return info;
+            })
+        },
+        getCurrentAudioTrack(): MediaInfo | null {
+            if (!player.isReady()) {
+                return null;
+            }
+
+            return player.getCurrentTrackFor("audio");
+        },
+        setCurrentAudioTrack(track: MediaInfo) {
             if (!player.isReady()) {
                 return;
             }
@@ -86,14 +101,6 @@ export const DashVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(f
         removeEventListener(event: string, listener: () => void) {
             player.off(event, listener);
         },
-        getAudioTracks(): MediaInfo[] {
-            if (!player.isReady()) {
-                return [];
-            }
-            return player.getTracksFor("audio").map((info: MediaInfo) => {
-                return info;
-            })
-        },
         debug(): any {
             return player;
         }
diff --git a/src/routes/player/video/RawVideoElement.tsx b/src/routes/player/video/RawVideoElement.tsx
index b957682..0d1c630 100644
--- a/src/routes/player/video/RawVideoElement.tsx
+++ b/src/routes/player/video/RawVideoElement.tsx
@@ -1,4 +1,4 @@
-import { forwardRef, PropsWithChildren, useImperativeHandle, useState } from "react";
+import {forwardRef, PropsWithChildren, useImperativeHandle, useState} from "react";
 import {Media} from "../../../api/models/Media";
 import {VideoApi} from "./VideoApi";
 import {MediaInfo} from "dashjs";
@@ -84,7 +84,10 @@ export const RawVideoElement = forwardRef<VideoApi, PropsWithChildren<Props>>(fu
         getAudioTracks(): MediaInfo[] {
             return [];
         },
-        setAudioTrack(track: MediaInfo) {
+        setCurrentAudioTrack(track: MediaInfo) {
+        },
+        getCurrentAudioTrack(): MediaInfo | null {
+            return null;
         },
         addEventListener(event: string, listener: () => void) {
             if (!videoElement) {
diff --git a/src/routes/player/video/VideoApi.ts b/src/routes/player/video/VideoApi.ts
index fd53529..24c7fb1 100644
--- a/src/routes/player/video/VideoApi.ts
+++ b/src/routes/player/video/VideoApi.ts
@@ -24,7 +24,9 @@ export interface VideoApi {
 
     getAudioTracks(): MediaInfo[]
 
-    setAudioTrack(track: MediaInfo): void
+    getCurrentAudioTrack(): MediaInfo | null
+
+    setCurrentAudioTrack(track: MediaInfo): void
 
     addEventListener(event: string, listener: () => void): void
 
diff --git a/src/util/media/useAudioTracks.ts b/src/util/media/useAudioTracks.ts
index 5076217..d22fb0e 100644
--- a/src/util/media/useAudioTracks.ts
+++ b/src/util/media/useAudioTracks.ts
@@ -1,25 +1,36 @@
-import {useEffect, useState} from "react";
+import {useCallback, useEffect, useState} from "react";
 import {VideoApi} from "../../routes/player/video/VideoApi";
 import {MediaInfo} from "dashjs";
 
-export const useAudioTracks = (video: VideoApi | null) => {
+export function useAudioTracks(video: VideoApi | null):
+    [MediaInfo[], MediaInfo | null, (track: MediaInfo) => void] {
     const [audioTracks, setAudioTracks] = useState<MediaInfo[]>([]);
-    useEffect(() => {
-        if (video !== null) {
-            if (video.canPlay()) {
+    const [currentTrack, setCurrentTrack] = useState<MediaInfo | null>(null);
+
+    const listener = useCallback(() => {
+        window.requestAnimationFrame(() => {
+            if (video !== null) {
                 setAudioTracks(video.getAudioTracks());
-            } else {
-                const listener = () => {
-                    window.requestAnimationFrame(() => {
-                        setAudioTracks(video.getAudioTracks());
-                    })
-                };
-                video.addEventListener(video.METADATA_EVENT, listener)
-                return () => {
-                    video.removeEventListener(video.METADATA_EVENT, listener)
-                }
+                setCurrentTrack(video.getCurrentAudioTrack);
             }
-        }
+        })
     }, [video]);
-    return audioTracks;
+
+    const updateAudioTracks = useCallback((track: MediaInfo) => {
+        video?.setCurrentAudioTrack(track);
+        listener();
+    }, [video, listener]);
+
+    useEffect(() => {
+        if (video?.canPlay()) {
+            setAudioTracks(video.getAudioTracks());
+            setCurrentTrack(video.getCurrentAudioTrack);
+        } else {
+            video?.addEventListener(video.METADATA_EVENT, listener)
+            return () => {
+                video?.removeEventListener(video.METADATA_EVENT, listener)
+            }
+        }
+    }, [listener, video]);
+    return [audioTracks, currentTrack, updateAudioTracks];
 }
-- 
GitLab