Skip to content

Commit

Permalink
feat(videodetail): add continue watching and watch history
Browse files Browse the repository at this point in the history
  • Loading branch information
royschut committed Jun 11, 2021
1 parent 820fb5c commit 7296635
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 21 deletions.
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

window.configLocation =
'https://' + configId + '.jwpapp.com/config.json';
window.configId = configId;
} else {
window.configLocation = './config.json';
}
Expand Down
11 changes: 7 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import ConfigProvider from './providers/ConfigProvider';
import QueryProvider from './providers/QueryProvider';
import UIStateProvider from './providers/uiStateProvider';
import './i18n/config';

import './styles/main.scss';

import { loadWatchHistory } from './store/WatchHistoryStore';
interface State {
error: Error | null;
}
Expand All @@ -19,11 +18,15 @@ class App extends Component {
error: null,
};

componentDidCatch (error: Error) {
componentDidCatch(error: Error) {
this.setState({ error });
}

render () {
componentDidMount() {
loadWatchHistory();
}

render() {
return (
<I18nextProvider i18n={getI18n()}>
<QueryProvider>
Expand Down
52 changes: 35 additions & 17 deletions src/containers/Cinema/Cinema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,59 @@ import React, { useContext, useEffect, useState } from 'react';
import type { Config } from 'types/Config';
import type { PlaylistItem } from 'types/playlist';

import { WatchHistoryStore, useWatchHistoryUpdater, WatchHistoryItem } from '../../store/WatchHistoryStore';
import { ConfigContext } from '../../providers/ConfigProvider';
import { addScript } from '../../utils/dom';

import styles from './Cinema.module.scss';

type Props = {
item: PlaylistItem;
onPlay: () => void;
onPause: () => void;
onPlay?: () => void;
onPause?: () => void;
};

const Cinema: React.FC<Props> = ({ item, onPlay, onPause }: Props) => {
const config: Config = useContext(ConfigContext);
const file = item?.sources[0]?.file;
const [initialized, setInitialized] = useState(false);
const file = item.sources[0]?.file;
const scriptUrl = `https://content.jwplatform.com/libraries/${config.player}.js`;
const [init, setInit] = useState(true);
const continueTillPercentage = 0.95;

const createWatchHistoryItem = (): WatchHistoryItem | undefined => {
const player = window.jwplayer && (window.jwplayer('cinema') as jwplayer.JWPlayer);

if (!player) return;

return {
mediaid: item.mediaid,
position: player.getPosition(),
} as WatchHistoryItem;
};
const watchHistory = WatchHistoryStore.useState((state) => state.watchHistory);
const updateWatchHistory = useWatchHistoryUpdater(createWatchHistoryItem);

useEffect(() => {
const getPlayer = () => window.jwplayer && (window.jwplayer('cinema') as jwplayer.JWPlayer);
const setupMedia = () => {
if (init) {
const player = getPlayer();
player.setup({ file });
player.on('ready', () => player.play());
player.on('play', () => onPlay());
player.on('pause', () => onPause());
setInit(false);
}
const loadVideo = () => {
const player = getPlayer();
const position = watchHistory.find((historyItem) => historyItem.mediaid === item.mediaid)?.position;
player.setup({ file });
player.on('ready', () => {
if (position && position < player.getDuration() * continueTillPercentage) {
player.seek(position);
}
player.play();
});
player.on('play', () => onPlay && onPlay());
player.on('pause', () => onPause && onPause());
};

if (config.player) {
if (!window.jwplayer) addScript(scriptUrl, setupMedia);
else setupMedia();
if (config.player && !initialized) {
getPlayer() ? loadVideo() : addScript(scriptUrl, loadVideo);
setInitialized(true);
}
}, [config.player, file, scriptUrl, onPlay, onPause, init]);
}, [item.mediaid, onPlay, onPause, config.player, file, scriptUrl, initialized, watchHistory, updateWatchHistory]);

return <div className={styles.Cinema} id="cinema" />;
};
Expand Down
49 changes: 49 additions & 0 deletions src/services/persist.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
*
* Service to persist either locally or remotely
* To localStorage or API
*
*/

import type { WatchHistoryItem } from '../store/WatchHistoryStore';

const LOCAL_STORAGE_KEY_WATCH_HISTORY = `watchhistory${window.configId ? `-${window.configId}` : ''}`;

const setItem = (key: string, value: string) => {
try {
window.localStorage.setItem(key, value);
} catch (error: unknown) {
console.error(error);
}
};

const getItem = (key: string) => {
try {
return window.localStorage.getItem(key);
} catch (error: unknown) {
console.error(error);
}
};

const parseJSON = (value?: string | null): unknown[] | undefined => {
if (!value) return;
try {
const ret = JSON.parse(value);

return ret;
} catch (error: unknown) {
return;
}
};

const loadWatchHistory = (): WatchHistoryItem[] => {
const localHistory = parseJSON(getItem(LOCAL_STORAGE_KEY_WATCH_HISTORY));
const watchHistory: WatchHistoryItem[] = localHistory ? (localHistory as WatchHistoryItem[]) : [];
return watchHistory;
};

const storeWatchHistory = (watchHistory: WatchHistoryItem[]): void => {
setItem(LOCAL_STORAGE_KEY_WATCH_HISTORY, JSON.stringify(watchHistory));
};

export { loadWatchHistory, storeWatchHistory };
63 changes: 63 additions & 0 deletions src/store/WatchHistoryStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Store } from 'pullstate';
import { useEffect } from 'react';

import * as persist from '../services/persist.service';

export type WatchHistoryItem = {
mediaid: string;
position: number;
};

type WatchHistoryStore = {
watchHistory: WatchHistoryItem[];
};

export const WatchHistoryStore = new Store<WatchHistoryStore>({
watchHistory: [],
});
WatchHistoryStore.subscribe(
(state) => state.watchHistory,
(watchHistory) => persist.storeWatchHistory(watchHistory),
);

export const loadWatchHistory = () => {
const watchHistory = persist.loadWatchHistory();

return (
watchHistory &&
WatchHistoryStore.update((state) => {
state.watchHistory = watchHistory;
})
);
};

export const useWatchHistoryUpdater = (createWatchHistoryItem: () => WatchHistoryItem | undefined): void => {
useEffect(() => {
const savePosition = () => {
const { watchHistory } = WatchHistoryStore.getRawState();
const item = createWatchHistoryItem();
if (!item) return;

const index = watchHistory.findIndex((historyItem) => historyItem.mediaid === item.mediaid);
if (index > -1) {
WatchHistoryStore.update((state) => {
state.watchHistory[index] = item;
});
} else {
WatchHistoryStore.update((state) => {
state.watchHistory.push(item);
});
}
};
const visibilityListener = () => document.visibilityState === 'hidden' && savePosition();

window.addEventListener('beforeunload', savePosition);
window.addEventListener('visibilitychange', visibilityListener);

return () => {
savePosition();
window.removeEventListener('beforeunload', savePosition);
window.removeEventListener('visibilitychange', visibilityListener);
};
}, []);
};
1 change: 1 addition & 0 deletions types/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
interface Window {
configLocation: configLocation;
configId: string;
jwplayer: (id: string) => unknown;
}

0 comments on commit 7296635

Please sign in to comment.