112
common/resources/client/player/state/fullscreen/fullscreen-slice.ts
Executable file
112
common/resources/client/player/state/fullscreen/fullscreen-slice.ts
Executable file
@@ -0,0 +1,112 @@
|
||||
import {StateCreator} from 'zustand';
|
||||
import {
|
||||
PlayerState,
|
||||
ProviderListeners,
|
||||
} from '@common/player/state/player-state';
|
||||
import {ScreenOrientation} from '@common/player/state/fullscreen/screen-orientation';
|
||||
import {IS_IPHONE} from '@common/utils/platform';
|
||||
import {FullscreenAdapter} from '@common/player/state/fullscreen/fullscreen-adapter';
|
||||
import {createNativeFullscreenAdapter} from '@common/player/state/fullscreen/create-native-fullscreen-adapter';
|
||||
import {createIphoneFullscreenAdapter} from '@common/player/state/fullscreen/create-iphone-fullscreen-adapter';
|
||||
import {PipSlice} from '@common/player/state/pip/pip-slice';
|
||||
|
||||
export interface FullscreenSlice {
|
||||
isFullscreen: boolean;
|
||||
canFullscreen: boolean;
|
||||
enterFullscreen: () => void;
|
||||
exitFullscreen: () => void;
|
||||
toggleFullscreen: () => void;
|
||||
initFullscreen: () => void;
|
||||
destroyFullscreen: () => void;
|
||||
}
|
||||
|
||||
type BaseSliceCreator = StateCreator<
|
||||
FullscreenSlice & PlayerState & PipSlice,
|
||||
[['zustand/immer', unknown]],
|
||||
[],
|
||||
FullscreenSlice
|
||||
>;
|
||||
|
||||
type StoreLice = BaseSliceCreator extends (...a: infer U) => infer R
|
||||
? (...a: [...U, Set<Partial<ProviderListeners>>]) => R
|
||||
: never;
|
||||
|
||||
const iPhoneProviderBlacklist = ['youtube'];
|
||||
|
||||
export const createFullscreenSlice: StoreLice = (set, get) => {
|
||||
let subscription: () => void | undefined;
|
||||
const orientation = new ScreenOrientation();
|
||||
let adapter: FullscreenAdapter | undefined;
|
||||
|
||||
const onFullscreenChange = async () => {
|
||||
const isFullscreen = adapter?.isFullscreen();
|
||||
if (isFullscreen) {
|
||||
// lock orientation to landscape
|
||||
orientation.lock();
|
||||
} else {
|
||||
orientation.unlock();
|
||||
}
|
||||
set({isFullscreen});
|
||||
};
|
||||
|
||||
const isSupported = (): boolean => {
|
||||
// iPhone only allows putting video element in fullscreen, and
|
||||
// there's no way to get access to it with YouTube iframe api
|
||||
if (IS_IPHONE && iPhoneProviderBlacklist.includes(get().providerName!)) {
|
||||
return false;
|
||||
}
|
||||
return adapter?.canFullScreen() ?? false;
|
||||
};
|
||||
|
||||
return {
|
||||
isFullscreen: false,
|
||||
canFullscreen: false,
|
||||
enterFullscreen: () => {
|
||||
if (!isSupported() || adapter?.isFullscreen()) return;
|
||||
|
||||
// exit pip if it's active
|
||||
if (get().isPip) {
|
||||
get().exitPip();
|
||||
}
|
||||
return adapter?.enter();
|
||||
},
|
||||
exitFullscreen: () => {
|
||||
if (!adapter?.isFullscreen()) return;
|
||||
return adapter.exit();
|
||||
},
|
||||
toggleFullscreen: () => {
|
||||
if (get().isFullscreen) {
|
||||
get().exitFullscreen();
|
||||
} else {
|
||||
get().enterFullscreen();
|
||||
}
|
||||
},
|
||||
initFullscreen: () => {
|
||||
subscription = get().subscribe({
|
||||
providerReady: ({el}) => {
|
||||
// when changing adapters, remove previous adapter events and exit fullscreen
|
||||
adapter?.unbindEvents();
|
||||
if (get().isFullscreen) {
|
||||
adapter?.exit();
|
||||
}
|
||||
// create new adapter, and if fullscreen is supported, bind events
|
||||
adapter = IS_IPHONE
|
||||
? createIphoneFullscreenAdapter(
|
||||
el as HTMLVideoElement,
|
||||
onFullscreenChange
|
||||
)
|
||||
: createNativeFullscreenAdapter(el, onFullscreenChange);
|
||||
const canFullscreen = isSupported();
|
||||
set({canFullscreen});
|
||||
if (canFullscreen) {
|
||||
adapter.bindEvents();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
destroyFullscreen: () => {
|
||||
get().exitFullscreen();
|
||||
subscription?.();
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user