@@ -100,6 +100,8 @@ let unSub: () => boolean; // Unsub from seek bus
100100const emit = defineEmits ([' loadedData' , ' seeked' , ' play' , ' pause' , ' ended' , ' loadedMetadata' ]);
101101
102102// Global State
103+ // So isAutoPlay determines if the video should auto start, and has no ui toggle
104+ // But isPlaylist determines if should navigate to next video at the end of current video and has a ui toggle called Autoplay ????????????
103105const { contextMenuItems, contextMenuStyle, contextMenuItemStyle, playbackHeatmap, ambientMode, lightMode, isAutoPlay, isPlaylist } = storeToRefs (useAppStore ());
104106const { updateViewCount } = useContentStore ();
105107const { setContextMenu } = useAppStore ();
@@ -320,8 +322,8 @@ const videoPopoverItems = computed(() => {
320322 },
321323 },
322324 {
323- text: ' Autoplay ' ,
324- title: ' Toggle Autoplay ' ,
325+ text: ' Playlist ' ,
326+ title: ` Toggle auto-playing the next ${ isAudio . value ? ' track ' : ' video ' } ` ,
325327 icon: MagePlaylist ,
326328 selectedIcon: ProiconsCheckmark ,
327329 selected: isPlaylist .value ?? false ,
@@ -477,7 +479,6 @@ const onPlayerPlay = async (override = false, recordProgress = true) => {
477479 onPlayerPause();
478480 return;
479481 } */
480-
481482 const playRequestId = ++ latestPlayRequestId .value ;
482483 try {
483484 isAutoPlay .value = false ;
@@ -869,23 +870,42 @@ const handleLoadUrlTime = async () => {
869870 handleManualSeek (seconds );
870871};
871872
872- const handleNext = (useAutoPlay = isAudio .value ) => {
873+ const handleNext = (useAutoPlay = isAudio .value || ( !! isPlaylist . value && ! isPaused . value ) ) => {
873874 if (! nextVideoURL .value ) {
875+ console .trace (' end of list' );
874876 toast (' Reached end of playlist' );
875877 return ;
876878 }
877879 isAutoPlay .value = useAutoPlay ;
878880 router .push (nextVideoURL .value );
879881};
880882
881- const handlePrevious = (useAutoPlay = isAudio .value ) => {
882- if (! previousVideoURL .value ) return ;
883+ const handlePrevious = (useAutoPlay = isAudio .value || (!! isPlaylist .value && ! isPaused .value )) => {
884+ if (! previousVideoURL .value ) {
885+ toast (' Reached start of playlist' );
886+ return ;
887+ }
883888 isAutoPlay .value = useAutoPlay ;
884889 router .push (previousVideoURL .value );
885890};
886891
892+ /**
893+ * Media key handler for playing and pausing the player.
894+ * @param explicitAction depending on the source (keybind or media session) explictly play / pause or simply toggle
895+ */
896+ const handlePlayPause = (explicitAction ? : ' play' | ' pause' ) => {
897+ if (explicitAction === ' play' ) onPlayerPlay ();
898+ else if (explicitAction === ' pause' ) onPlayerPause ();
899+ else handlePlayerToggle ();
900+ };
901+
902+ // Debounced actions shared by keybinds and media session action handlers
903+ const debouncedHandleNext = debounce (handleNext , 50 , { leading: true , trailing: false });
904+ const debouncedHandlePrevious = debounce (handlePrevious , 50 , { leading: true , trailing: false });
905+ const debouncedHandlePlayPause = debounce (handlePlayPause , 50 , { leading: true , trailing: false });
906+
887907const handleKeyBinds = (event : KeyboardEvent , override = false ) => {
888- const keyBinds = [' ArrowLeft' , ' ArrowRight' , ' ArrowUp' , ' ArrowDown' , ' l' , ' N' , ' j' , ' k' , ' m' , ' c' , ' ' , ' f' , ' MediaTrackNext' , ' MediaTrackPrevious' , ' MediaPlayPause' ];
908+ const keyBinds = [' ArrowLeft' , ' ArrowRight' , ' ArrowUp' , ' ArrowDown' , ' l' , ' N' , ' P ' , ' j' , ' k' , ' m' , ' c' , ' ' , ' f' , ' MediaTrackNext' , ' MediaTrackPrevious' , ' MediaPlayPause' ];
889909
890910 if (! keyBinds .includes (event .key )) return ;
891911 if (isInputLikeElement (event .target , event .key ) && ! override ) return ;
@@ -901,7 +921,11 @@ const handleKeyBinds = (event: KeyboardEvent, override = false) => {
901921 break ;
902922 case ' N' :
903923 if (! event .shiftKey ) return ;
904- handleNext (false );
924+ handleNext ();
925+ break ;
926+ case ' P' :
927+ if (! event .shiftKey ) return ;
928+ handlePrevious ();
905929 break ;
906930 case ' m' :
907931 handleMute ();
@@ -913,7 +937,7 @@ const handleKeyBinds = (event: KeyboardEvent, override = false) => {
913937 case ' ' :
914938 case ' MediaPlayPause' :
915939 event .preventDefault ();
916- handlePlayerToggle ();
940+ debouncedHandlePlayPause ();
917941 break ;
918942 case ' f' :
919943 handleFullScreen ();
@@ -928,11 +952,11 @@ const handleKeyBinds = (event: KeyboardEvent, override = false) => {
928952 break ;
929953 case ' MediaTrackNext' :
930954 event .preventDefault ();
931- handleNext ();
955+ debouncedHandleNext ();
932956 break ;
933957 case ' MediaTrackPrevious' :
934958 event .preventDefault ();
935- handlePrevious ();
959+ debouncedHandlePrevious ();
936960 break ;
937961 default :
938962 break ;
@@ -942,15 +966,16 @@ const handleKeyBinds = (event: KeyboardEvent, override = false) => {
942966const handleMediaSessionEvents = () => {
943967 if (! (' mediaSession' in navigator )) {
944968 console .warn (' Media Session API is not supported in this browser.' );
969+ isMediaSession .value = false ;
945970 return ;
946971 }
947972 isMediaSession .value = true ;
948973 navigator .mediaSession .setActionHandler (' play' , () => {
949- onPlayerPlay ( );
974+ debouncedHandlePlayPause ( ' play ' );
950975 });
951976
952977 navigator .mediaSession .setActionHandler (' pause' , () => {
953- onPlayerPause ( );
978+ debouncedHandlePlayPause ( ' pause ' );
954979 });
955980
956981 navigator .mediaSession .setActionHandler (' seekbackward' , () => {
@@ -962,11 +987,11 @@ const handleMediaSessionEvents = () => {
962987 });
963988
964989 navigator .mediaSession .setActionHandler (' previoustrack' , () => {
965- handlePrevious ();
990+ debouncedHandlePrevious ();
966991 });
967992
968993 navigator .mediaSession .setActionHandler (' nexttrack' , () => {
969- handleNext ();
994+ debouncedHandleNext ();
970995 });
971996
972997 navigator .mediaSession .setActionHandler (' seekto' , (details : MediaSessionActionDetails ) => {
0 commit comments