Save video playback position in browser localstorage
This commit is contained in:
@@ -3,6 +3,8 @@ import { createEventDispatcher } from "svelte"
|
||||
import { flip } from "svelte/animate"
|
||||
import FilePicker from "./FilePicker.svelte"
|
||||
import { file_type } from "./FileUtilities.svelte";
|
||||
import { get_video_position } from "./../lib/VideoPosition.mjs"
|
||||
import ProgressBar from "./../util/ProgressBar.svelte"
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
export let list = {
|
||||
@@ -112,6 +114,7 @@ const drop = (e, index) => {
|
||||
{/if}
|
||||
|
||||
{#each list.files as file, index (file)}
|
||||
{@const vp = get_video_position(file.id)}
|
||||
<a
|
||||
href="#item={index}"
|
||||
class="file"
|
||||
@@ -145,7 +148,13 @@ const drop = (e, index) => {
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if vp !== null}
|
||||
<div class="grow"></div>
|
||||
<ProgressBar no_margin used={vp.pos} total={vp.dur}/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{file.name}
|
||||
</a>
|
||||
{/each}
|
||||
@@ -195,6 +204,8 @@ const drop = (e, index) => {
|
||||
text-decoration: none;
|
||||
}
|
||||
.icon_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 3px;
|
||||
height: 148px;
|
||||
border-radius: 6px;
|
||||
@@ -248,4 +259,7 @@ const drop = (e, index) => {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.grow {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher, tick } from "svelte";
|
||||
import { video_position } from "../../lib/VideoPosition.mjs";
|
||||
import BandwidthUsage from "./BandwidthUsage.svelte";
|
||||
import IconBlock from "../../layout/IconBlock.svelte";
|
||||
let dispatch = createEventDispatcher()
|
||||
@@ -140,6 +141,7 @@ const video_keydown = e => {
|
||||
on:play={() => playing = true }
|
||||
on:ended={() => dispatch("next", {})}
|
||||
on:keydown={video_keydown}
|
||||
use:video_position={() => file.id}
|
||||
>
|
||||
<source src={file.get_href} type={file.mime_type} />
|
||||
</video>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher, tick } from "svelte";
|
||||
import { video_position } from "../../lib/VideoPosition.mjs";
|
||||
import { fs_path_url } from "../FilesystemAPI.mjs";
|
||||
let dispatch = createEventDispatcher()
|
||||
|
||||
@@ -107,6 +108,7 @@ const video_keydown = e => {
|
||||
on:play={() => playing = true }
|
||||
on:ended={() => dispatch("open_sibling", 1)}
|
||||
on:keydown={video_keydown}
|
||||
use:video_position={() => $nav.base.sha256_sum.substring(0, 8)}
|
||||
>
|
||||
<source src={fs_path_url($nav.base.path)} type={$nav.base.file_type} />
|
||||
</video>
|
||||
|
101
svelte/src/lib/VideoPosition.mts
Normal file
101
svelte/src/lib/VideoPosition.mts
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
type VideoPositions = {
|
||||
[key: string]: VideoPosition
|
||||
}
|
||||
|
||||
export type VideoPosition = {
|
||||
time: number
|
||||
pos: number
|
||||
dur: number
|
||||
}
|
||||
|
||||
const storage_key = "video_positions"
|
||||
const expiry_time = 28 * 24 * 60 * 60 * 1000
|
||||
|
||||
let position_cache: VideoPositions | null = null
|
||||
export const get_video_positions = () => {
|
||||
if (position_cache !== null) {
|
||||
return position_cache
|
||||
}
|
||||
|
||||
let video_positions = JSON.parse(window.localStorage.getItem(storage_key)) as VideoPositions
|
||||
if (video_positions === null) {
|
||||
return {} as VideoPositions
|
||||
}
|
||||
return video_positions
|
||||
}
|
||||
|
||||
export const save_video_position = (id: string, position: number, duration: number) => {
|
||||
const video_positions = get_video_positions()
|
||||
|
||||
// Add our new entry
|
||||
video_positions[id] = {
|
||||
time: (new Date).getTime(),
|
||||
pos: position,
|
||||
dur: duration,
|
||||
}
|
||||
|
||||
// Remove old entries
|
||||
const expiry_thresh = (new Date).getTime() - expiry_time
|
||||
for (const key in video_positions) {
|
||||
if (video_positions[key].time < expiry_thresh) {
|
||||
delete video_positions[key]
|
||||
console.debug("Delete old video position", key)
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated object
|
||||
window.localStorage.setItem(storage_key, JSON.stringify(video_positions))
|
||||
|
||||
// Update the cache
|
||||
position_cache = video_positions
|
||||
}
|
||||
|
||||
export const get_video_position = (id: string) => {
|
||||
const video_positions = get_video_positions()
|
||||
if (video_positions[id] === undefined) {
|
||||
return null
|
||||
}
|
||||
return video_positions[id]
|
||||
}
|
||||
|
||||
export const video_position = (node: HTMLVideoElement, get_id: () => string) => {
|
||||
let last_time = 0
|
||||
|
||||
const loadeddata = (e: Event) => {
|
||||
last_time = 0
|
||||
|
||||
const vp = get_video_position(get_id())
|
||||
if (vp === null || vp.pos === 0 || vp.dur === 0) {
|
||||
return
|
||||
} else if (vp.pos / vp.dur > 0.95) {
|
||||
// If the video is more than 95% complete we don't do anything
|
||||
console.debug("Video is at end, not setting time")
|
||||
return
|
||||
}
|
||||
|
||||
(e.target as HTMLVideoElement).currentTime = vp.pos
|
||||
last_time = vp.pos
|
||||
}
|
||||
|
||||
const timeupdate = (e: Event) => {
|
||||
const vid = (e.target as HTMLVideoElement)
|
||||
|
||||
// If the current timestamp is more than ten seconds off the last
|
||||
// timestamp we saved, then we save the new timestamp
|
||||
if (Math.abs(vid.currentTime - last_time) > 10) {
|
||||
save_video_position(get_id(), vid.currentTime, vid.duration)
|
||||
last_time = vid.currentTime
|
||||
}
|
||||
}
|
||||
|
||||
node.addEventListener("loadeddata", loadeddata)
|
||||
node.addEventListener("timeupdate", timeupdate)
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener("loadeddata", loadeddata)
|
||||
node.removeEventListener("timeupdate", timeupdate)
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ export let used = 0
|
||||
export let animation = "ease"
|
||||
export let speed = 1000
|
||||
export let no_animation = false
|
||||
export let no_margin = false
|
||||
export let style = ""
|
||||
let percent = 0
|
||||
$: {
|
||||
@@ -21,7 +22,7 @@ $: {
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="progress_bar_outer" style={style}>
|
||||
<div class="progress_bar_outer" style={style} class:no_margin>
|
||||
<div
|
||||
class="progress_bar_inner"
|
||||
class:no_animation
|
||||
@@ -49,4 +50,7 @@ $: {
|
||||
.no_animation {
|
||||
transition-property: none;
|
||||
}
|
||||
.no_margin {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user