diff --git a/src/components/App.css b/src/components/App.css index 33854b1..173e266 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -96,6 +96,13 @@ color: #888; } +.App-library-list-item-time-stamp { + color: #888; + float: right; + margin-right: 1em; + font-size: 0.8em; +} + .App-library-list-item:last-child { border-bottom: 1px solid #ddd; } diff --git a/src/components/App.js b/src/components/App.js index 87068cd..aea64e4 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -4,6 +4,8 @@ import { ScrollContext } from 'react-router-scroll-4'; import { extractAudioFromVideo, extractFrameImageFromVideo } from '../library'; +import { secondsToTimestamp } from '../util/string'; + import './App.css'; import WidthWrapper from './WidthWrapper.js'; @@ -13,13 +15,31 @@ import AddCollection from './AddCollection.js'; import ImportEpwing from './ImportEpwing.js'; const VideoListItem = (props) => { - const { videoId, collection, name } = props; + const { videoId, collection, name, playbackPosition} = props; const hasSubs = collection.videos.get(videoId).subtitleTracks.size > 0; + // Get the current playback position. We first check if we have a "live" + // version of the position, if the user has visited the video this session. + // Otherwise we get the position that was loaded from the database on + // application launch. + var position; + if (collection.videos.get(videoId).playbackPosition != null) { + position = collection.videos.get(videoId).playbackPosition; + } else { + position = playbackPosition; + } + + // Build the timestamp for time watched. + var time_stamp = ""; + if (position > 2.0) { // Only give a time stamp if enough has been watched. + time_stamp += "Watched "; + time_stamp += secondsToTimestamp(position); + } + return (
  • - {name} + {name} {time_stamp}
  • ); @@ -68,21 +88,21 @@ class App extends Component { {title.parts.seasonEpisodes.length ? ( ) : null} {title.parts.episodes.length ? ( ) : null} {title.parts.others.length ? ( ) : null} @@ -112,7 +132,7 @@ class App extends Component { ) : ( - + ) )} diff --git a/src/library/index.js b/src/library/index.js index 61cef70..85dacf5 100644 --- a/src/library/index.js +++ b/src/library/index.js @@ -8,6 +8,7 @@ import { ensureKuromojiLoaded, createAutoAnnotatedText } from '../util/analysis' import { detectIso6393 } from '../util/languages'; import { createTimeRangeChunk, createTimeRangeChunkSet } from '../util/chunk'; import { extractAudio, extractFrameImage } from '../util/ffmpeg'; +import createStorageBackend from '../storage'; const LOCAL_PREFIX = 'local:'; @@ -108,6 +109,16 @@ const listDirs = async (dir) => { }; export const getCollectionIndex = async (collectionLocator) => { + const storage = await createStorageBackend(); + const playbackPos = async (vid_id) => { + const positionStr = await storage.getItemMaybe('playback_position/' + encodeURIComponent(collectionLocator) + '/' + encodeURIComponent(vid_id)); + if (!positionStr) { + return 0; + } else { + return JSON.parse(positionStr); + } + }; + if (collectionLocator.startsWith(LOCAL_PREFIX)) { const baseDirectory = collectionLocator.slice(LOCAL_PREFIX.length); @@ -130,6 +141,7 @@ export const getCollectionIndex = async (collectionLocator) => { series: false, videoId: vid.id, parts: null, + playbackPosition: await playbackPos(vid.id), }); } @@ -158,6 +170,7 @@ export const getCollectionIndex = async (collectionLocator) => { series: false, videoId: vids[0].id, parts: null, + playbackPosition: await playbackPos(vids[0].id), }); } else { const seasonEpisodes = []; @@ -173,6 +186,7 @@ export const getCollectionIndex = async (collectionLocator) => { seasonNumber: sNum, episodeNumber: eNum, videoId: vid.id, + playbackPosition: await playbackPos(vid.id), }); } else { const eMatch = EPISODE_PATTERN.exec(vid.name); @@ -181,11 +195,13 @@ export const getCollectionIndex = async (collectionLocator) => { episodes.push({ episodeNumber: eNum, videoId: vid.id, + playbackPosition: await playbackPos(vid.id), }); } else { others.push({ name: vid.name, videoId: vid.id, + playbackPosition: await playbackPos(vid.id), }); } } diff --git a/src/mainActions.js b/src/mainActions.js index 12b9fd4..dede70a 100644 --- a/src/mainActions.js +++ b/src/mainActions.js @@ -46,6 +46,7 @@ const TitleRecord = new Record({ series: undefined, videoId: undefined, // only defined if not a series parts: undefined, // only defined if a series + playbackPosition: undefined, }); const VideoRecord = new Record({ @@ -53,7 +54,7 @@ const VideoRecord = new Record({ name: undefined, videoURL: undefined, subtitleTracks: new IMap(), // id -> SubtitleTrackRecord - playbackPosition: 0, + playbackPosition: null, loadingSubs: false, }); @@ -115,6 +116,7 @@ export default class MainActions { name: vid.name, videoURL: vid.url, subtitleTracks: new IMap(subTrackKVs), + playbackPosition: vid.playbackPosition, // remaining fields are OK to leave as default })]); } @@ -126,6 +128,7 @@ export default class MainActions { series: title.series, videoId: title.videoId, // only defined if not a series parts: title.parts, // only defined if a series + playbackPosition: title.playbackPosition, })); } diff --git a/src/util/string.js b/src/util/string.js index abf90f1..772e88a 100644 --- a/src/util/string.js +++ b/src/util/string.js @@ -8,3 +8,26 @@ export const removePrefix = (s, prefix) => { } export const cpSlice = (s, cpBegin, cpEnd) => [...s].slice(cpBegin, cpEnd).join(''); + +// Converts from seconds (float) to a nicely formatted timestamp (string). +// +// The timestamp will look like this: "hh:mm:ss", but will omit unneeded digits. +// For example, if the input is 61 second, the timestamp will be "1:01". +export const secondsToTimestamp = (seconds) => { + const hrs = Math.floor(seconds / (60*60)); + seconds -= hrs * 60 * 60; + const mnts = Math.floor(seconds / 60); + seconds -= mnts * 60; + const secs = Math.floor(seconds); + + var time_stamp = ""; + if (hrs > 0) { + time_stamp += hrs + ":"; + time_stamp += ("00" + mnts).slice(-2) + ":"; + } else { + time_stamp += mnts + ":"; + } + time_stamp += ("00" + secs).slice(-2); + + return time_stamp; +} \ No newline at end of file