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 keepsSharedArrayBuffer. 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
currentTimesetter 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 standardhiddenattribute 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.DashFallbackplays bare-BaseURLmanifests Shaka rejects via the demuxer. - Live-stream UI:
LIVEbadge that jumps to the live edge, DVR-window seeking, Auto-mode quality badge. - Custom request headers (
headersattribute / property): Auth tokens / signed headers across manifest + segments, progressive HTTP, thumbnails, and the encrypted source. JSON-string attribute or object property; alsoPlayerConfig.headers. - Audio-only data-saver mode (
audioonlyattribute /audioOnlyproperty): 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(200not206) now play via a forward-only sliding window (linear mode); a newlinearmodeevent lets the UI adapt. - MPEG-5 LCEVC decoding (
lcevc/lcevcurlattributes): 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 URLplays.m3u8/.mpd/.ismby 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; bumphls.jsto^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/5xxmap 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
srcchange. - Cover-art backdrop blur via CSS
filter:blur()(works in Safari < 17); audio-onlyposterrenders as album art. - Audio-only replay/loop restarts the native
<audio>; no context menu withoutcontrols.
[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
SourceAdapterfor<movi-player>andMoviPlayer(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 โ
subtitledelayattribute,subtitleDelayproperty,setSubtitleDelay()/getSubtitleDelay()API methods, and a newsubtitledelaychangeCustomEvent. 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,subtitleedgeattributes, 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_cuesthat usesAVDISCARD_ALLso 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
innerHTMLrewrite 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 withsrclang(orlabel) become parallel language tracks; the player surfaces the audio-language menu andgetAudioLangs()/selectAudioLang()work exactly as for muxed tracks. Default pick: explicitdefault/data-defaultโ first locale match (navigator.languageprefix) โ 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 nokindare recognized. Readssrclang,label, anddata-format(defaults to VTT, setsrtfor SRT sidecars). Lets integrators ship full caption configurations as plain HTML without wiring upsource({ subtitles: [...] })from JS. - Host Fullscreen Handoff: New cancelable
movi-fullscreen-requestCustomEvent +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:
filerevokedCustomEvent fires when the browser silently revokes aFilehandle (mobile background / memory pressure).FileSourceraces each chunk read against an 8s timeout โ no more demuxer hanging forever โ and surfaces the failure via a one-shotonRevokedcallback onMoviPlayer. 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 customDataSource(webview'sFileproxy delegatesslice().arrayBuffer()to extension-hostfs.createReadStreamchunks); 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 viamovi.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) and8640p(16K) labels in both native and HLS stats paths โ previously bucketed as 4K.
Changed โ
- HLS Volume Controls Now Visible: Volume button,
ArrowUp/ArrowDownhotkeys, and volume OSD were gated only on muxed/split audio, so HLS streams (audio inside the native<video>) had no mute control. Consolidated behindhasAudibleSource()so the HLS path is covered too. - Audio Decode Stays Running While Muted: The demux loop no longer drops audio packets when muted โ
AudioRendererkeeps 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) andCanvasRendererchased 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:
EncryptedHttpSourcehoisted 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/dvdsubtitledecoders renamed todvbsub/dvdsubto 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:
formatAudioBadgepreviously 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. Mentioncoi-serviceworkeras a static-host workaround.
Fixed โ
- ~1s Fullscreen Freeze: ResizeObserver fires repeatedly during the fullscreen animation. Each call set
canvas.widthtwice (inupdateCanvasSizeandCanvasRenderer.resize), clearing the WebGL framebuffer on every burst. Coalesced same-size resizes, dropped the duplicate width/height assignment, dropped the<video>.width/heightno-ops. - WebGL Context Loss Recovery (Mobile): Capture canvas to JPEG on
visibilitychangeโhiddenwhile the GL context is alive; on return, hide instantly ifgl.isContextLost()is false, otherwise leave it up sohandleContextLost/handleContextRestoredcan run recovery without a corrupt framebuffer flashing through. RestoredisLoadingclear soinitializePlayerdoesn'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
RESOLUTIONcausedconfigure(0,0)on the canvas renderer, producing a black frame that only cleared on the next ResizeObserver tick. Defer configure to<video> loadedmetadatawhen manifest dims are missing. - HLS Quality Badge / Fit Animation: Re-emit
tracksChangeonLEVEL_SWITCHEDso 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 newfirstBufferMediaTimeanchor 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 withpointerType="mouse"from a touch tap) tomatchMedia("(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-runscheckSecurityHeaders(), 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
processLoophad 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 accepthasNativeAudio(). - 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
maxScheduledMediaTimefreezes andgetAudioClock()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
seekTargetTimefilter 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. getCurrentPlaybackTimeFrozen When Paused:updateActiveSubtitlecalled viasetSubtitleCuesduring pause can no longer jump to a wall-clock-driven time.- PiP Exit Buffer Resize: Invalidate
_lastCanvasW/Hon 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
currentTimeSets: Overlapping seeks now collapse into a single tail seek instead of queueing them all. preventScrollon 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 โ
postertimeAttribute: Generate a native-resolution poster from any timestamp without an explicitposterURL. 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 asrcchange.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 everysrcchange so playlist-style flows never leak state between sources. Safe to call when nothing is loaded.playingGetter: Read-only boolean that'strueonly while the player is actively playing โ distinguishes it fromready,loading,seeking, andbufferingstates (precise inverse ofpaused).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
webkitdirectoryfallback). 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 duringisLoadingand flushes it frominitializePlayer()'s finally block โ matchesHTMLMediaElementbehavior. Previously bailed silently when called during load.- Software Decoder Fallback Per-Source: Choosing "Try software" no longer sticks across
srcchanges. The next video gets a fresh hardware-decode attempt; theswattribute is cleared on dispose. - Encrypted Playback Protocol:
EncryptedHttpSourcerewritten โ 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
buffersizeattribute. 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_consoleanddrop_debuggerso release builds ship without dev-only logging. - Build Stability:
app:releasescript 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
/proxyEmpty 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
SxxExxcode (e.g.,Show.S05E01.Title).
Security โ
- Worker Referer Allowlist:
/proxyand/eproxyendpoints now gate requests by Referer to block hotlinking from unauthorized origins. - Worker Magic-Byte Validation:
/proxyresponses 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, andhdrtoggles now persist via OPFS alongsidevolume,muted, andplaybackRate. 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-Dispositionfilename 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:
themecolorattribute now flows to--movi-primary-lightand--movi-primary-darkviacolor-mix, so active menu items and highlights follow the custom theme. titleAttribute: 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
readysoplay()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
Gkey or context menu. Works in fullscreen (letterbox) and normal mode (external wrapper).ambientmodeattribute. - Split Source Support: Separate video, audio, and subtitle file URLs via
videosrc,audiosrc,subtitlesrcattributes. - 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:
drmandlicenseurlattributes 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:
Vsubtitles,Baudio,+/-speed,Lloop,Ustable volume,HHDR,PPiP,Gambient,Aaspect ratio. - Aspect Ratio Controls:
Akey cycles contain/cover/fill/zoom. Sub-menu with icons in context menu and bottom controls. - Stable Volume: DynamicsCompressorNode for loudness normalization. Opt-in via
stablevolumeattribute. - Nerd Stats: Press
Ifor codec, resolution, FPS, decoder type, buffer health, color info, and live network/disk activity graph. - Timeline: Press
Tfor 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
Rto rotate 90ยฐ. Metadata rotation auto-applied. Disabled during PiP. - Keyboard Shortcuts Panel: Press
?to view all shortcuts. - Resume Playback:
resumeattribute 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 โ endedstate 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.onLinelags. - 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
shadowRootdirectly. - PiP frame freeze on tab switch with
isPiPActiveguard. - 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.