Skip to content

Changelog โ€‹

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased] โ€‹

[0.3.2] - 2026-06-17 โ€‹

Added โ€‹

  • Desktop app โ€” Windows / macOS / Linux (desktop/): a new Electron app wrapping the player engine. Plays MKV, HEVC, AV1 and 4K HDR locally through the same WebCodecs + FFmpeg-WASM pipeline, served from a cross-origin-isolated localhost server so the WASM demuxer keeps SharedArrayBuffer. Includes drag-and-drop, native Open With / file associations for every supported format, URL playback through a built-in proxy (no CORS limits), a multi-file playlist with auto-advance, recent files, an Open-URL dialog with clipboard paste, full-window keyboard shortcuts, and a native always-on-top Picture-in-Picture window (Electron doesn't render Document PiP, so PiP is a real OS window that hands the source off and resumes on return). Cross-platform installers (dmg / nsis / AppImage + deb) and document icons via electron-builder.

Fixed โ€‹

  • Software-decoder fallback without WebCodecs: when the browser has no WebCodecs VideoDecoder (e.g. Firefox, especially on mobile) the player now falls back to the WASM software decoder instead of failing โ€” video was left stuck buffering while audio fell back on its own.
  • Seek before ready is queued: the currentTime setter now holds a seek requested before the player is ready and applies it on the next seekable state, so a hand-off / early seek no longer stalls on a still-loading source.
  • <movi-player hidden> now hides: the component's :host { display: block } was overriding the UA [hidden] rule, so the standard hidden attribute did nothing.
  • Centre play/pause + loading spinner positioning: sit at the true centre on the initial / autoplay-off screen and lift slightly to balance the controls bar only once playback has started; they also animate in compact / PiP layouts. (Previously keyed off a :host:has() rule that some engines, e.g. Electron's Chromium, don't apply to shadow descendants.)

[0.3.1] - 2026-06-10 โ€‹

Added โ€‹

  • MPEG-DASH playback (.mpd) (closes #9): DASH manifests now play alongside HLS through the adaptive pipeline.
  • Unified adaptive streaming via Shaka (HLS / DASH / Smooth): Shaka is the primary engine for .m3u8 / .mpd / .ism; hls.js and dash.js remain as automatic fallbacks. DashFallback plays bare-BaseURL manifests Shaka rejects via the demuxer.
  • Live-stream UI: LIVE badge that jumps to the live edge, DVR-window seeking, Auto-mode quality badge.
  • Custom request headers (headers attribute / property): Auth tokens / signed headers across manifest + segments, progressive HTTP, thumbnails, and the encrypted source. JSON-string attribute or object property; also PlayerConfig.headers.
  • Audio-only data-saver mode (audioonly attribute / audioOnly property): Play just the audio to save CPU/bandwidth โ€” muxed files skip the video decode, streams switch to an audio-only rendition, split sources stop downloading the video body. Live-toggleable, forces the album-art strip UI.
  • Non-range (no-Range) server playback: Servers that ignore Range (200 not 206) now play via a forward-only sliding window (linear mode); a new linearmode event lets the UI adapt.
  • MPEG-5 LCEVC decoding (lcevc / lcevcurl attributes): Opt-in LCEVC enhancement-layer decoding for adaptive streams.
  • Muted-autoplay fallback for split native-audio tracks: rolls video muted + shows the tap-to-unmute pill instead of freezing.
  • Extension: detect .mpd (DASH) URLs in page scan.
  • VS Code extension: adaptive streaming via URL โ€” Movi: Open Video from URL plays .m3u8 / .mpd / .ism by loading them directly in the player engine (no host byte-range proxy); progressive files still use the proxy.
  • VS Code extension: .ts (MPEG-TS) added to the open-file dialog (not the single-click association).

Changed โ€‹

  • DRM key-system order: Widevine โ†’ PlayReady โ†’ FairPlay.
  • Manifests load directly, never via /proxy: fixes relative segment resolution and the proxy content-type allowlist for .m3u8 / .mpd / .ism.
  • Size resolution hardened: HEAD โ†’ ranged GET โ†’ plain GET retry chain for CDNs that strip Content-Length.
  • deps: add shaka-player ^4.11.2, dashjs ^5.2.0; bump hls.js to ^1.6.16.

Fixed โ€‹

  • Robust startup: visible play affordance on blocked autoplay, guarded first-play seek, background-tab autoplay deferred until visible.
  • Don't flash wrapper errors mid-fallback: no spurious "Try Software Decoding" while falling back behind Shaka.
  • HTTP errors surface real messages: 403 / 404 / 5xx map to access-denied / not-found / server-error.
  • Wake lock: skip while hidden, retry transient failure, re-acquire on visible/resize.
  • Don't collapse a loading/errored video into the 56px audio strip on resize.
  • App proxy: don't proxy same-origin URLs (522 loop); don't magic-sniff tiny range probes.
  • Volume slider opens on first touch; ambient re-applies after src change.
  • Cover-art backdrop blur via CSS filter:blur() (works in Safari < 17); audio-only poster renders as album art.
  • Audio-only replay/loop restarts the native <audio>; no context menu without controls.

[0.3.0] - 2026-06-02 โ€‹

Added โ€‹

  • Signalsmith Stretch audio rate-change pipeline: Replaced SoundTouch with Signalsmith Stretch as the sole pitch-preserving time-stretcher.
  • First-class audio-only support with strip UI: Audio-only files play through the canvas pipeline with a dedicated audio strip UI (cover art, title, progress bar, controls).
  • Muted-autoplay fallback with tap-to-unmute: When autoplay is blocked, the player starts muted and shows an "unmute" pill overlay.
  • Cover art display for audio: JS-only album art extraction via an isolated demuxer context.
  • Custom SourceAdapter for <movi-player> and MoviPlayer (closes #7): Plug any custom byte protocol directly into the element or player.
  • File-source preload settling gate: play() and resume gated until initial preload fills.
  • YouTube-style centre play button: Always-visible large play/pause icon.
  • Unified controls chrome โ€” dark gradient bar + redesigned OSD: Opaque backgrounds replace backdrop-filter blurs.
  • Extension: playlist shuffle, autoplay toggle, next button
  • Extension: hover-probe links + opt-in toggle + flag detection
  • Compare page (/compare): Side-by-side native vs movi-player.
  • VS Code extension โ€” URL streaming, multi-window commands, Activity Bar entry
  • Homepage redesign

Changed โ€‹

  • 4K playback rate cap raised to 2x (only 8K+ capped at 1.5x)
  • Renderer queue split for 4K vs 8K
  • UI update loop throttled to 4Hz
  • Volume slider uses perceptual (log) gain curve
  • Ambient mode FBO mirror (no more full canvas readback)
  • Thumbnail hover latency cut
  • Dropped backdrop-filter blur from all UI surfaces
  • README redesigned, browser support updated (Firefox 130+)
  • AGENTS.md shipped in package

Fixed โ€‹

  • Decode: RASL leading pictures after CRA/BLA seek, DoVi/HDR HEVC on hardware, open-GOP HEVC fallback, tiny packet drop, AV1 Temporal Delimiter OBU
  • Seek: prefer IDR fall back to CRA, resume into buffering on timeout
  • Playback: mid-playback decode-error recovery, auto-start on rate restore, rapid seeks stuck, stall detection during recovery, audio tail on rate change, EOF detection
  • Audio: log gain curve, pitch-shift at startup, audio-only shortcuts, near-end seek clipping
  • UI: controls flash, play/pause icon state, centre play flickering, cursor hiding, loop toggle replay
  • Canvas: display-p3 fallback, rec2100-pq preservation
  • Thumbnail: recreate dead WebCodecs decoder
  • Extension: video link detection, loop replays
  • HTTP: surface server errors

[0.2.3] - 2026-05-07 โ€‹

Added โ€‹

  • Subtitle Delay / Offset (closes #4): Shift subtitle timing relative to video โ€” subtitledelay attribute, subtitleDelay property, setSubtitleDelay() / getSubtitleDelay() API methods, and a new subtitledelaychange CustomEvent. Sign convention matches VLC/mpv (positive = subtitles later). UI cap ยฑ300s with widened input. Z/X hotkeys nudge by 100ms per press; OSD shows the current offset. Auto-prefetch when delay becomes non-zero so negative offsets work (cues from stream positions ahead of the demuxer cursor). Applied at the renderer's active-cue check so a single offset works for text and image (PGS/DVB) cues without re-decoding.
  • Subtitle Customization Panel: subtitlesize, subtitlecolor, subtitlebg, subtitleedge attributes, plus an in-player customize panel persisted to localStorage. Size multiplier drives both bitmap (PGS/VOBSUB) and text (SRT/ASS/VTT) cues; edge style applies to text subs.
  • Subtitle Transcript Browser: Full-cover panel with search, click-to-seek, active-cue highlight, italic/bold/entity rendering, and delay-aware timestamps. Click on a live caption opens the transcript at the current cue. Backed by a native movi_prefetch_subtitle_cues that uses AVDISCARD_ALL so a 700 MB scan touches only subtitle packets, not every audio/video body.
  • Karaoke Captions for VTT: Tag-only-token folding, min-width anchor measured offscreen, render-key cache to stop the 60fps innerHTML rewrite that prevented fade-in during playback. Format-aware backdrop (VTT-only).
  • Premuxed Quality Menu: Multiple <source data-height="..."> children give a YouTube-style quality picker for plain MP4/MKV files โ€” no HLS manifest needed. Adopt/release native <audio> across switches preserves the user-activation token so the next switch isn't blocked by autoplay policy.
  • Multi-Language Audio via <source kind="audio">: Two or more audio <source> tags with srclang (or label) become parallel language tracks; the player surfaces the audio-language menu and getAudioLangs() / selectAudioLang() work exactly as for muxed tracks. Default pick: explicit default / data-default โ†’ first locale match (navigator.language prefix) โ†’ first track. Single <source kind="audio"> continues to use the legacy split-audio path.
  • External Subtitles via <track>: Standard <video>-style declarative markup โ€” <track kind="subtitles">, kind="captions", or no kind are recognized. Reads srclang, label, and data-format (defaults to VTT, set srt for SRT sidecars). Lets integrators ship full caption configurations as plain HTML without wiring up source({ subtitles: [...] }) from JS.
  • Host Fullscreen Handoff: New cancelable movi-fullscreen-request CustomEvent + setHostFullscreen(active) method. Lets embedders (VS Code webviews, custom app shells) take over fullscreen with their own chrome while keeping the player's toolbar icon, OSD, and context-menu label in sync. Fullscreen state is now reflected in the context-menu label.
  • File Revoked Event: filerevoked CustomEvent fires when the browser silently revokes a File handle (mobile background / memory pressure). FileSource races each chunk read against an 8s timeout โ€” no more demuxer hanging forever โ€” and surfaces the failure via a one-shot onRevoked callback on MoviPlayer.
  • MoviPlayer.hasAudibleSource(): Unified gate covering muxed audio, split native <audio>, and HLS audio (which lives inside the hidden native <video>). Used internally to decide whether to show volume controls / accept volume hotkeys.
  • VS Code Extension: New vscode-extension/ package (Marketplace 0.2.5). Webview-hosted player registered as a CustomEditor โ€” single-click opens any MP4/MKV/HEVC/AV1/WebM/MOV/TS file VS Code can't natively play. True streaming via a custom DataSource (webview's File proxy delegates slice().arrayBuffer() to extension-host fs.createReadStream chunks); memory cost drops from O(filesize) to ~chunk size, so multi-GB and 8K HDR files no longer hit the 4 GB Blob limit. Movi fullscreen toggle hides workbench chrome with auto-cleanup on crash. OS wake lock (caffeinate -i / systemd-inhibit / SetThreadExecutionState) held during fullscreen. Multi-window playback via movi.openInNewWindow. Output channel surfaces bundled-player logs.
  • Web App Explorer-Style Playlist: Folder hierarchy tree with collapsible groups + guide rail, multi-select, live search (folders auto-expand on match), keyboard navigation (Tab toggle, Up/Down/Enter, Esc). Thumbnails + metadata cached in IndexedDB so reopening the same files skips every WASM call. SEO overhaul, landing animations, gradient circle brand mark.
  • Chrome Extension Explorer Playlist: Folder tree, breadcrumb, badges, progress, drag/drop, multi-file + folder picking. Shared isolated WASM instance (2-instance budget) for thumbnail generation, cached in IndexedDB across sessions. Install detection on moviplayer.com hides the "Add to Chrome" prompt when the extension is already present. Gradient circle play-button branding to match the main app.
  • Stats 8K / 16K Tiers: 4320p (8K) and 8640p (16K) labels in both native and HLS stats paths โ€” previously bucketed as 4K.

Changed โ€‹

  • HLS Volume Controls Now Visible: Volume button, ArrowUp / ArrowDown hotkeys, and volume OSD were gated only on muxed/split audio, so HLS streams (audio inside the native <video>) had no mute control. Consolidated behind hasAudibleSource() so the HLS path is covered too.
  • Audio Decode Stays Running While Muted: The demux loop no longer drops audio packets when muted โ€” AudioRenderer keeps gain at 0 instead. Fixes the "atak atak" judder on unmute, where the audio clock pivoted forward to the demuxer's lookahead (~1โ€“3s ahead of presentation) and CanvasRenderer chased it 25%/frame.
  • Bluetooth A2DP Keepalive: Pause path now suspends the AudioContext but starts a near-silent looping <audio> element so the OS audio session stays claimed. BT devices stop dropping/re-pairing on every pause without re-introducing the "2โ€“3s jump-ahead on resume" regression.
  • DPR-Scaled Canvas Backbuffer: Canvas backbuffer scales with devicePixelRatio (capped at 2ร—) so downsampling 4K/8K sources stays sharp. CSS dimensions remain in logical pixels.
  • Encrypted Source Static Import: EncryptedHttpSource hoisted to a top-level import โ€” no more async boundary on every encrypted load. Matches the other source adapters.
  • FFmpeg Bumped to n8.1.1: Picks up upstream point-release fixes on the n8.1 branch. dvbsubtitle / dvdsubtitle decoders renamed to dvbsub / dvdsub to match.
  • Subtitle Default Sizing: Bumped the text-subtitle base size and replaced the desktop-era 60px floor on bottom padding with a height-proportional 8% (24px floor) so subtitles don't crowd into the middle of small embeds.
  • Menu Animations: Pop-in / pop-out on the audio, subtitle, quality, and speed dropdowns plus a fade between the customize panel and track list. Bottom-controls dropdowns enforce one-at-a-time. Click on the player area closes any open menu instead of toggling play/pause.
  • Keyboard Shortcuts Ignored While Typing in Inputs: Hotkeys no longer fire when an input/textarea inside the shadow DOM is focused.
  • Audio Menu Always Shows Language Code: formatAudioBadge previously dropped the language code when channel info was available, so muxed tracks from MKV/MP4 displayed only "AAC Stereo" with no way to tell languages apart.
  • COOP/COEP Hard-Required: README/docs corrected โ€” the player hard-blocks without Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp. Surfaces a "Security Headers Missing" diagnostic instead of a cryptic timeout. Mention coi-serviceworker as a static-host workaround.

Fixed โ€‹

  • ~1s Fullscreen Freeze: ResizeObserver fires repeatedly during the fullscreen animation. Each call set canvas.width twice (in updateCanvasSize and CanvasRenderer.resize), clearing the WebGL framebuffer on every burst. Coalesced same-size resizes, dropped the duplicate width/height assignment, dropped the <video>.width/height no-ops.
  • WebGL Context Loss Recovery (Mobile): Capture canvas to JPEG on visibilitychangeโ†’hidden while the GL context is alive; on return, hide instantly if gl.isContextLost() is false, otherwise leave it up so handleContextLost/handleContextRestored can run recovery without a corrupt framebuffer flashing through. Restored isLoading clear so initializePlayer doesn't early-return after long minimize. 5s cooldown between audio-resync seeks prevents stutter loops on slow software audio decoders.
  • HLS First-Frame Black: Manifests without RESOLUTION caused configure(0,0) on the canvas renderer, producing a black frame that only cleared on the next ResizeObserver tick. Defer configure to <video> loadedmetadata when manifest dims are missing.
  • HLS Quality Badge / Fit Animation: Re-emit tracksChange on LEVEL_SWITCHED so the gear badge reflects the active rendition in Auto mode. Skip smoothing-state reset on same-size canvas resizes so fit-mode toggles can lerp instead of snapping; clone last frame in the direct-render path so HLS paused redraws have a source to animate against.
  • Pauseโ†’Resume 2โ€“3s Jump-Ahead: Pause now preserves scheduled buffers and sync anchors exactly, so the first packet drained from pendingPrebufferPackets (whose timestamp is the demuxer's lookahead) doesn't become the new firstBufferMediaTime anchor on resume.
  • Aspect-Ratio Change While Paused: Fit-mode change now repaints the canvas after a seek-to-paused and animates via a dedicated RAF loop instead of snapping. Poster overlay tracks the active fit mode, lets pointer events pass through so dblclick / gesture handlers still fire, and stays hidden until a source is set.
  • Mobile Speaker Tap: Switched from PointerEvent.pointerType (Android Chrome synthesizes click with pointerType="mouse" from a touch tap) to matchMedia("(hover: none)") as the primary touch signal. composedPath() to cross shadow boundary in the close-volume listener. Mobile media query no longer hides the slider on .active.
  • Embed Security Headers Diagnostic: Every src= change now re-runs checkSecurityHeaders(), so reused players surface "Security Headers Missing" instead of a cryptic "Failed to open media: Timeout at 0".
  • Pause Buffering Loop / Single-Track Streams: Pause buffer loop required both audio and video targets via AND, so audio-only / video-only streams never satisfied it and ran to the 3000-packet safety cap (~30s of demux), surfacing as a burst of cache-read spam after pause. Now only checks targets for tracks that exist.
  • A/V Drift Loop on Hardware Burst Decoders: Hardware decoders that emit 8K frames in bursts queued many future-PTS frames; on >60Hz displays the fallback drained them faster than wall-clock and tripped the audio-desync resync seek loop. Reject frames more than one frame interval ahead of playback time.
  • Audio-less Video in Background Tab: Without an audio track, the background processLoop had no backpressure (video decode skipped, no audio buffer to fill), so the demuxer raced to EOF in seconds. Pause on hide and auto-resume on visible.
  • Volume Keys for Native Audio Sources: Hotkeys + OSD gated on getAudioTracks().length (empty for split video/audio sources). Now also accept hasNativeAudio().
  • Buffered Bar Stability: Buffered bar is now monotonic between seeks; pause-time buffering no longer pushes past HttpSource's buffered end and trigger a window-resetting refetch.
  • PGS Subtitles On-Canvas: Image-subtitle overlay was sized in DPR-scaled buffer pixels, pushing the flex-anchored bitmap off-screen on retina. Switched to the canvas's CSS rect (matching the text-subtitle path).
  • Skip A/V Desync Check When Muted: Demux loop drops audio decode while muted, so maxScheduledMediaTime freezes and getAudioClock() clamps to a stale value โ€” disabled the 500ms desync detector while muted.
  • No Corrective Seek After Unmute: Reset the desync cooldown on unmute so the audio clock can catch up first instead of forcing a resync seek (visible as a loading shutter).
  • Progress Handle at 0%: Dropped the Math.max(1, โ€ฆ) floor so the handle sits at 0% at the start instead of jumping in from 1%.
  • Pre-Play Seek: Re-arm the seekTargetTime filter on first-play re-seek so Open-GOP recovery frames (1โ€“2s behind the seek target) get dropped instead of presented; matching drop on resume from pause.
  • getCurrentPlaybackTime Frozen When Paused: updateActiveSubtitle called via setSubtitleCues during pause can no longer jump to a wall-clock-driven time.
  • PiP Exit Buffer Resize: Invalidate _lastCanvasW/H on PiP exit so the buffer resizes back to host dimensions instead of staying pinned at PiP resolution.
  • Seek OSD Accuracy: Track the actual delta between the pre-seek time and the clamped target instead of a fixed 10s step. Anchor chained presses on the previous target. Dismiss the OSD on a boundary hit / sub-second / NaN delta.
  • Coalesce Rapid currentTime Sets: Overlapping seeks now collapse into a single tail seek instead of queueing them all.
  • preventScroll on Hover Focus: focus() on mouseenter no longer yanks the page when the player is partly off-screen.
  • Subtitle Re-render on Resize via rAF: Previously a burst per ResizeObserver tick stalled the presentation loop on window drags.
  • Centre Non-VTT Subtitle Lines: Multi-line SRT cues (e.g. "- A long line\n- short") now sit at the player's centre instead of drifting left.
  • Worker /proxy Probe Failures: Transient probe errors no longer get misreported as 415 Unsupported Media Type.

Documentation โ€‹

  • WebCodecs team outreach playbook (docs/webcodecs-outreach.md).

[0.2.2] - 2026-04-26 โ€‹

Added โ€‹

  • postertime Attribute: Generate a native-resolution poster from any timestamp without an explicit poster URL. Accepts "10%", "5", "1:30", or "0:01:30". Uses an isolated thumbnail pipeline (WASM + ThumbnailBindings), respects rotation metadata, and is race-guarded so in-flight generators can't paint stale frames after a src change.
  • dispose() Method: Tears down the internal player and resets transient UI (subtitles, timeline, time, title, generated poster) back to the no-source state. Called automatically on every src change so playlist-style flows never leak state between sources. Safe to call when nothing is loaded.
  • playing Getter: Read-only boolean that's true only while the player is actively playing โ€” distinguishes it from ready, loading, seeking, and buffering states (precise inverse of paused).
  • MoviElement.cleanVideoTitle(filename) Static: Utility exposed for playlist UIs to derive the same cleaned title the player uses internally โ€” useful for computing the resume localStorage key (movi-resume:<cleanVideoTitle(name)>).
  • Folder Playlist (web demo app): Sidebar/below-player playlist via File System Access API (with webkitdirectory fallback). YouTube-style items with thumbnail, duration, HDR chip, codec/quality/size meta, and watched-progress bar. Lazy thumbnail generation, natural-sort, autoplay-next toggle, drag-and-drop multi-file support.

Changed โ€‹

  • play() Semantics: Now queues a play intent during isLoading and flushes it from initializePlayer()'s finally block โ€” matches HTMLMediaElement behavior. Previously bailed silently when called during load.
  • Software Decoder Fallback Per-Source: Choosing "Try software" no longer sticks across src changes. The next video gets a fresh hardware-decode attempt; the sw attribute is cleared on dispose.
  • Encrypted Playback Protocol: EncryptedHttpSource rewritten โ€” block prefetch high-water/low-water tuning, concurrent-stream cap, getPosition() reports the real read cursor, and parent position field is kept in sync so buffer math stays honest. Encrypted-server ported to match the new protocol.
  • Buffer Tuning: Runtime tuning of prefetch high-water, refill threshold, and block cache cap via the existing buffersize attribute. README/docs corrected to clarify the value is in megabytes (not seconds) and applies to both HTTP and encrypted sources.
  • Production Bundles: Re-enabled terser drop_console and drop_debugger so release builds ship without dev-only logging.
  • Build Stability: app:release script ties build + R2 upload + worker deploy into a single command. Build version cache-bust scoped to the quoted __BUILD_VERSION__ literal so unrelated lines aren't rewritten.

Fixed โ€‹

  • Post-Seek A/V Sync: Cap the post-seek audio gap at 200ms โ€” when the first video frame after a seek arrives late (sparse keyframes / slow HEVC+HDR decoders), sync the clock to video time and drop stale audio instead of syncing to the earliest audio packet. Small gaps still prefer audio for continuity.
  • Pre-Play Seek Position: Scrubbing the timeline before pressing play no longer resets to 0 โ€” the first-play poster-seek now reads clock.getTime() instead of a hardcoded start time. Pipeline is flushed on user seek so prebuffered start audio doesn't briefly play before jumping to the target.
  • Fully-Cached Buffered Duration: Buffered range now reports the full media duration when the file is fully cached, instead of stopping at the last network read.
  • Buffer Indicator Race: Collapsed the seek-race scan sweep that could draw a phantom buffered range mid-seek.
  • Encrypted Thumbnails: Share the main source for thumbnail reads instead of opening a parallel session โ€” cuts redundant token churn. Concurrent stream cap prevents seek-storm thrash. Hardened thumbnail read failures (no more fragile retry/cooldown loop).
  • Worker /proxy Empty 206: Retry empty 206 responses from upstream before streaming back, so transient origin hiccups don't surface as broken playback.
  • Worker Probe Failures: Transient probe errors no longer get misreported as 415 Unsupported Media Type.
  • TMDb Title Parser: Detect TV shows when the episode title trails the SxxExx code (e.g., Show.S05E01.Title).

Security โ€‹

  • Worker Referer Allowlist: /proxy and /eproxy endpoints now gate requests by Referer to block hotlinking from unauthorized origins.
  • Worker Magic-Byte Validation: /proxy responses are validated against expected media magic bytes before being streamed back, mitigating MIME confusion attacks.

[0.2.1] - 2026-04-16 โ€‹

Added โ€‹

  • Persistent Preferences: stableVolume, ambientMode, and hdr toggles now persist via OPFS alongside volume, muted, and playbackRate. User toggles win over HTML attribute defaults on subsequent loads.
  • Split-Source Volume Control: Volume button now visible when a separate native audio element is loaded, even if the video file has no muxed audio track.
  • Smart Title Extraction: VLC-style filename cleaning strips release tags, codecs, and quality markers from tab titles. Content-Disposition filename used when the server provides one.
  • Chrome Extension: Local file playback via popup file picker, drag-and-drop onto the player page, and a redesigned popup layout.

Changed โ€‹

  • Context Menu: Slide-panel variant now only used on touch devices (pointer: coarse); narrow desktop windows get the regular hover-driven menu.
  • Context Menu Scrolling: Max-height clamped to player height with subtle scrollbar styling so tall menus stay accessible on short players.
  • Theme Color Cascade: themecolor attribute now flows to --movi-primary-light and --movi-primary-dark via color-mix, so active menu items and highlights follow the custom theme.
  • title Attribute: No longer triggers the browser's native tooltip on hover โ€” title is rendered only by the in-player overlay.
  • Subtitle/Audio Track Menus: Show language codes alongside track labels for clarity.

Fixed โ€‹

  • Short Video Stutter: Prebuffer media before ready so play() doesn't immediately stall on short clips.
  • Background Audio at 50/60 fps: Skip video decode while hidden so audio keeps flowing on high-fps content.
  • Narrow Viewport Controls: Buttons, gaps, and center play button tightened on viewports โ‰ค 480px to prevent the controls bar from overflowing the player box on iPhone 12 Pro-class widths.
  • Empty State Placement: "No Video" placeholder no longer clips into the controls bar on short/narrow players.
  • OSD Speed Icon: Correct speed icon shown when playback rate changes via hotkeys/context menu.

[0.2.0] - 2026-04-09 โ€‹

Added โ€‹

  • Ambient Mode: Dynamic letterbox glow that samples video colors in real-time. Smooth 60fps color transitions via WebGL clearColor. Toggle with G key or context menu. Works in fullscreen (letterbox) and normal mode (external wrapper). ambientmode attribute.
  • Split Source Support: Separate video, audio, and subtitle file URLs via videosrc, audiosrc, subtitlesrc attributes.
  • PGS Image Subtitles: Bitmap subtitle decoding with zlib decompression support.
  • Network Disconnect Recovery: Intelligent CORS vs transient network failure detection (3-strike threshold). Online-event-aware backoff for instant retry on reconnection. Auto re-seek on recovery. 30s timeout on offline wait.
  • Document Picture-in-Picture: Floating video window with play/pause, seek, mute, progress bar, time display, keyboard shortcuts, and back-to-tab button. Portrait video sizing. Rotation save/restore on PiP enter/exit.
  • DRM Support: drm and licenseurl attributes for HLS streams with Widevine/FairPlay via EME API.
  • HLS Quality Menu: Duplicate resolutions show bitrate (e.g., "1080p ยท 5000 kbps").
  • HLS Nerd Stats: Video codec, resolution, quality, frame rate, bitrate, buffer, HLS level, bandwidth, live latency, frames decoded/dropped.
  • VLC-style Shortcuts: V subtitles, B audio, +/- speed, L loop, U stable volume, H HDR, P PiP, G ambient, A aspect ratio.
  • Aspect Ratio Controls: A key cycles contain/cover/fill/zoom. Sub-menu with icons in context menu and bottom controls.
  • Stable Volume: DynamicsCompressorNode for loudness normalization. Opt-in via stablevolume attribute.
  • Nerd Stats: Press I for codec, resolution, FPS, decoder type, buffer health, color info, and live network/disk activity graph.
  • Timeline: Press T for auto-generated thumbnail strip with chapter support. Arrow key navigation, click-to-seek.
  • Chapter Support: Extract chapters from video metadata. Chapter markers on progress bar, chapter titles in seek tooltip.
  • Video Rotation: Press R to rotate 90ยฐ. Metadata rotation auto-applied. Disabled during PiP.
  • Keyboard Shortcuts Panel: Press ? to view all shortcuts.
  • Resume Playback: resume attribute saves position to localStorage with resume/start-over dialog.
  • Encrypted Playback: AES-256-GCM chunked encryption with HMAC-SHA256 signed requests.
  • Background Audio: Video keeps playing audio when tab is in background via Web Worker timer fallback.
  • Chrome Extension: Popup with "Paste & Play" and "Play from Computer", context menu on video links, play button overlay on detected URLs.
  • Privacy Policy: Published at docs site for Chrome Web Store compliance.

Changed โ€‹

  • Buffering state now stops presentation loop so frames accumulate for reliable recovery.
  • Buffering exit requires video frames ready (with 3s fallback for async decoder delays).
  • Pause during buffering allowed from all UI controls (click, keyboard, buttons, PiP, context menu).
  • buffering โ†’ ended state transition allowed for EOF during rebuffer.
  • Invalid packet size at EOF treated as EOF (not fatal error) for FFmpeg stale buffer data.
  • HDR icon changed to text badge style in OSD and context menu (matches bottom controls).
  • Extension description rewritten to remove excessive format keywords (Chrome Web Store compliance).
  • Console logs dropped in production build.

Fixed โ€‹

  • Hardware decoder error recovery with keyframe cache and software fallback.
  • Seek and play/pause during buffering state.
  • Network disconnect causing permanent CORS misclassification when navigator.onLine lags.
  • Stale stream loops from leaked online event listeners during backoff.
  • Multiple concurrent fetch loops after network recovery.
  • Clock advancing during buffering (presentation loop consuming frames).
  • PiP rotation clipping โ€” rotation reset on PiP enter, restored on close.
  • PiP portrait video oversized โ€” height-limited sizing for portrait aspect ratios.
  • Pause-seek loading stuck โ€” VideoDecoder.flush() 1s timeout with reset+reconfigure fallback.
  • EOF not triggering โ€” relaxed condition with 0.5s tolerance.
  • PiP canvas restore using shadowRoot directly.
  • PiP frame freeze on tab switch with isPiPActive guard.
  • EncryptedHttpSource network resilience matching HttpSource.
  • Nerd stats graph fullscreen positioning and CSS specificity.

[0.1.5] - 2026-02-15 โ€‹

Added โ€‹

  • Pitch preservation for playback rate changes
  • Pitch preservation support for HLS playback
  • MediaSession API integration for background playback and media controls
  • HTTPS support for local development environment

Changed โ€‹

  • Simplified error messages to be more concise and consistent
  • Replaced all hardcoded purple colors with CSS variables (--movi-primary) for full theme customization
  • Enhanced center play button with theme color by default
  • Updated loading spinner with responsive sizing and theme-aware colors

Fixed โ€‹

  • Improved playback stability with enhanced error handling and timeout management
  • Resolved audio-video sync issues with hardware decoding
  • Distinguished 403/401/404 errors from CORS errors for better error reporting
  • CORS errors now propagate immediately instead of waiting for timeout
  • Title bar z-index now properly positioned below control menus in mobile view
  • Center play button backdrop blur now enabled on mobile/touch devices
  • Controls no longer auto-hide when menus are open on mobile

[0.1.4] - 2026-02-11 โ€‹

Fixed โ€‹

  • Resolved video stalling during playback and improved A/V sync
  • Playback speed changes now take immediate effect on audio
  • Auto-unmute when volume slider is moved while muted
  • Mute button now correctly toggles audio muting

Previous Versions โ€‹

See git commit history for changes in versions prior to 0.1.4.

Released under the Apache-2.0 License. Privacy Policy