movi-player 0.1.3 → 0.1.5-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +63 -3
  2. package/dist/core/MoviPlayer.d.ts +4 -4
  3. package/dist/core/MoviPlayer.d.ts.map +1 -1
  4. package/dist/core/MoviPlayer.js +94 -145
  5. package/dist/core/MoviPlayer.js.map +1 -1
  6. package/dist/demuxer.cjs +106 -64
  7. package/dist/demuxer.js +111 -69
  8. package/dist/element.cjs +7515 -7220
  9. package/dist/element.js +1157 -862
  10. package/dist/index.cjs +7515 -7220
  11. package/dist/index.js +1157 -862
  12. package/dist/movi-sw.js +40 -0
  13. package/dist/player.cjs +311 -269
  14. package/dist/player.js +311 -269
  15. package/dist/render/AudioRenderer.d.ts +5 -0
  16. package/dist/render/AudioRenderer.d.ts.map +1 -1
  17. package/dist/render/AudioRenderer.js +54 -9
  18. package/dist/render/AudioRenderer.js.map +1 -1
  19. package/dist/render/CanvasRenderer.d.ts +1 -0
  20. package/dist/render/CanvasRenderer.d.ts.map +1 -1
  21. package/dist/render/CanvasRenderer.js +46 -11
  22. package/dist/render/CanvasRenderer.js.map +1 -1
  23. package/dist/render/MoviElement.d.ts +42 -0
  24. package/dist/render/MoviElement.d.ts.map +1 -1
  25. package/dist/render/MoviElement.js +759 -158
  26. package/dist/render/MoviElement.js.map +1 -1
  27. package/dist/source/HttpSource.d.ts +1 -1
  28. package/dist/source/HttpSource.d.ts.map +1 -1
  29. package/dist/source/HttpSource.js +189 -133
  30. package/dist/source/HttpSource.js.map +1 -1
  31. package/dist/source/ThumbnailHttpSource.d.ts.map +1 -1
  32. package/dist/source/ThumbnailHttpSource.js +74 -39
  33. package/dist/source/ThumbnailHttpSource.js.map +1 -1
  34. package/dist/utils/service-worker.d.ts +33 -0
  35. package/dist/utils/service-worker.d.ts.map +1 -0
  36. package/dist/utils/service-worker.js +184 -0
  37. package/dist/utils/service-worker.js.map +1 -0
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -7,14 +7,59 @@
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
8
8
  [![Browser Support](https://img.shields.io/badge/browsers-Chrome%2094%2B%20%7C%20Safari%2016.4%2B%20%7C%20Edge%2094%2B-brightgreen.svg)](https://caniuse.com/webcodecs)
9
9
 
10
- 👉 **Install:** `npm i movi-player`
11
- 👉 **Docs:** [mrujjwalg.github.io/movi-player](https://mrujjwalg.github.io/movi-player/)
10
+ 👉 **Install:** `npm i movi-player`
11
+ 👉 **Docs:** [mrujjwalg.github.io/movi-player](https://mrujjwalg.github.io/movi-player/)
12
12
  👉 **Demo:** [movi-player-examples.vercel.app](https://movi-player-examples.vercel.app/element.html)
13
13
 
14
- ![Movi Player Showcase](docs/images/element.gif)
14
+ ### Element Module (Full UI)
15
+
16
+ ![Movi Player Element](docs/images/element.gif)
17
+
18
+ 👉 [View Live Demo](https://movi-player-examples.vercel.app/element.html) | [See Code Example](https://github.com/mrujjwalg/movi-player-examples/blob/main/element.html)
19
+
20
+ ### Player Module (Custom UI)
21
+
22
+ ![Custom Player](docs/images/custom.gif)
23
+
24
+ 👉 [View Live Demo](https://movi-player-examples.vercel.app/youtube.html) | [See Code Example](https://github.com/mrujjwalg/movi-player-examples/blob/main/youtube.html)
15
25
 
16
26
  > 🚀 **No Server-Side Processing Required!** — All video parsing, demuxing, and decoding happens entirely in the browser using FFmpeg WASM & WebCodecs. Multiple audio/subtitle tracks are supported without any conversion or processing!
17
27
 
28
+ ## ⚠️ Important: CORS & Headers
29
+
30
+ Because Movi-Player uses **WebAssembly** and **SharedArrayBuffer** for high-performance streaming, your server needs to support:
31
+
32
+ 1. **Range Requests:** Required for seeking in large files.
33
+ 2. **CORS Headers:** If your video is on a different domain.
34
+ 3. **COI Headers (Optional):** For maximum performance (Zero-copy), set:
35
+ - `Cross-Origin-Opener-Policy: same-origin`
36
+ - `Cross-Origin-Embedder-Policy: require-corp`
37
+
38
+ **Can't modify server headers?** Use a **Service Worker** to inject COI headers client-side:
39
+
40
+ ```javascript
41
+ // sw.js
42
+ self.addEventListener('fetch', (event) => {
43
+ event.respondWith(
44
+ fetch(event.request).then((response) => {
45
+ const newHeaders = new Headers(response.headers);
46
+ newHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp');
47
+ newHeaders.set('Cross-Origin-Opener-Policy', 'same-origin');
48
+ return new Response(response.body, {
49
+ status: response.status,
50
+ statusText: response.statusText,
51
+ headers: newHeaders,
52
+ });
53
+ })
54
+ );
55
+ });
56
+
57
+ // Register in your app
58
+ if ('serviceWorker' in navigator) {
59
+ navigator.serviceWorker.register('/sw.js');
60
+ }
61
+ ```
62
+
18
63
  ---
19
64
 
20
65
  ## ⚡ TL;DR
@@ -199,6 +244,8 @@ fileInput.addEventListener("change", async (e) => {
199
244
 
200
245
  ![Demuxer Overview](docs/images/demuxer.webp)
201
246
 
247
+ 👉 [View Live Demo](https://movi-player-examples.vercel.app/demuxer.html) | [See Code Example](https://github.com/mrujjwalg/movi-player-examples/blob/main/demuxer.html)
248
+
202
249
  ```typescript
203
250
  import { Demuxer, HttpSource, FileSource } from "movi-player/demuxer";
204
251
 
@@ -245,6 +292,8 @@ Movi-Player's modular design makes it perfect for a wide range of applications:
245
292
 
246
293
  ### Demuxer Module (50KB)
247
294
 
295
+ 👉 [View Live Demo](https://movi-player-examples.vercel.app/demuxer.html) | [See Code Example](https://github.com/mrujjwalg/movi-player-examples/blob/main/demuxer.html)
296
+
248
297
  - **Media Asset Management**: Catalog video libraries without playing files
249
298
  - **Video Validators**: Check uploaded files against platform requirements
250
299
  - **HDR Detection**: Automatically tag HDR content in video pipelines
@@ -254,6 +303,10 @@ Movi-Player's modular design makes it perfect for a wide range of applications:
254
303
 
255
304
  ### Player Module (180KB)
256
305
 
306
+ ![Custom Player UI](docs/images/custom.gif)
307
+
308
+ 👉 [View Live Demo](https://movi-player-examples.vercel.app/youtube.html) | [See Code Example](https://github.com/mrujjwalg/movi-player-examples/blob/main/youtube.html)
309
+
257
310
  - **Custom Video Players**: Build branded players with custom UI
258
311
  - **Educational Platforms**: Interactive learning videos with quiz overlays
259
312
  - **Multi-Language Platforms**: Netflix-style audio/subtitle switching
@@ -557,6 +610,13 @@ The `<movi-player>` element supports standard video attributes plus enhancements
557
610
  renderer="canvas" <!-- canvas | mse -->
558
611
  sw="auto" <!-- auto (default) | true | false -->
559
612
  fps="0" <!-- Custom frame rate override -->
613
+ gesturefs <!-- Gestures only in fullscreen -->
614
+ nohotkeys <!-- Disable keyboard shortcuts -->
615
+ startat="0" <!-- Start playback at time (seconds) -->
616
+ fastseek <!-- Enable ±10s skip controls -->
617
+ doubletap="true" <!-- Enable double-tap to seek -->
618
+ themecolor="#4CAF50" <!-- Custom theme color -->
619
+ buffersize="0" <!-- Buffer size in seconds (0=auto) -->
560
620
  ></movi-player>
561
621
  ```
562
622
 
@@ -30,6 +30,7 @@ export declare class MoviPlayer extends EventEmitter<PlayerEventMap> {
30
30
  private previewInitPromise;
31
31
  private disableAudio;
32
32
  private muted;
33
+ private wasPlayingBeforeRebuffer;
33
34
  private animationFrameId;
34
35
  private wakeLock;
35
36
  private seekingToKeyframe;
@@ -71,11 +72,10 @@ export declare class MoviPlayer extends EventEmitter<PlayerEventMap> {
71
72
  private static readonly DEMUX_TIMEOUT;
72
73
  private eofReached;
73
74
  /**
74
- * Dedicated handler for video seek completion.
75
- * Clears the seek flag, synchronizes the clock if the video jumped ahead,
76
- * and flushes any buffered audio packets to start playback in sync.
75
+ * Internal handler for seek completion when first target frame is found.
76
+ * Clears the seek flag, synchronizes clock, and transitions to final state.
77
77
  */
78
- private handleVideoSeekCompletion;
78
+ private notifySeekCompletion;
79
79
  /**
80
80
  * Main Playback Loop
81
81
  */
@@ -1 +1 @@
1
- {"version":3,"file":"MoviPlayer.d.ts","sourceRoot":"","sources":["../../src/core/MoviPlayer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MoviPlayer.d.ts","sourceRoot":"","sources":["../../src/core/MoviPlayer.ts"],"names":[],"mappings":"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"}
@@ -47,6 +47,7 @@ export class MoviPlayer extends EventEmitter {
47
47
  // Debug flag to disable audio processing
48
48
  disableAudio = false; // Set to true to disable audio for debugging
49
49
  muted = false; // Mute state
50
+ wasPlayingBeforeRebuffer = false; // Track if we were playing before entering rebuffering state
50
51
  // Playback Loop
51
52
  animationFrameId = null;
52
53
  // WakeLock to prevent screen sleep during playback
@@ -136,9 +137,9 @@ export class MoviPlayer extends EventEmitter {
136
137
  frame.close();
137
138
  return;
138
139
  }
139
- // Video reached target! Clear the flag to ensure sync
140
+ // Video reached target! Clear the flag to ensure sync and transition to final state
140
141
  if (this.seekTargetTime >= 0) {
141
- this.handleVideoSeekCompletion(frameTime);
142
+ this.notifySeekCompletion(frameTime);
142
143
  }
143
144
  this.videoRenderer.queueFrame(frame);
144
145
  }
@@ -569,38 +570,58 @@ export class MoviPlayer extends EventEmitter {
569
570
  static DEMUX_TIMEOUT = 10000; // 10 seconds timeout for demux operations
570
571
  eofReached = false;
571
572
  /**
572
- * Dedicated handler for video seek completion.
573
- * Clears the seek flag, synchronizes the clock if the video jumped ahead,
574
- * and flushes any buffered audio packets to start playback in sync.
573
+ * Internal handler for seek completion when first target frame is found.
574
+ * Clears the seek flag, synchronizes clock, and transitions to final state.
575
575
  */
576
- handleVideoSeekCompletion(videoTime) {
576
+ notifySeekCompletion(time) {
577
+ if (!this.waitingForVideoSync)
578
+ return;
577
579
  const seekTarget = this.seekTargetTime;
578
580
  this.seekTargetTime = -1;
579
- // If we were waiting for video sync, flush buffered audio
580
- if (this.waitingForVideoSync) {
581
- this.waitingForVideoSync = false;
582
- // Sync correction: Match clock to actual video start time
583
- // If we are just seeking (waitingForVideoSync was true), allow smaller tolerance (0.01) to ensure the frame is displayed (clock >= frameTime)
584
- if (videoTime > seekTarget + 0.01) {
585
- Logger.debug(TAG, `Video jumped ahead (${seekTarget.toFixed(3)}s -> ${videoTime.toFixed(3)}s). Syncing clock.`);
586
- this.clock.seek(videoTime);
587
- this.pendingAudioPackets = this.pendingAudioPackets.filter((p) => p.timestamp >= videoTime - 0.05);
581
+ this.waitingForVideoSync = false;
582
+ this.seekingToKeyframe = false; // Also clear keyframe skip flag
583
+ Logger.debug(TAG, `Seek completion at ${time.toFixed(3)}s (target: ${seekTarget.toFixed(3)}s)`);
584
+ // Sync correction: Match clock to actual video/audio start time
585
+ // Allow small tolerance (0.01) to ensure the frame is displayed (clock >= frameTime)
586
+ if (time > seekTarget + 0.01) {
587
+ Logger.debug(TAG, `Stream jumped ahead. Syncing clock to ${time.toFixed(3)}s.`);
588
+ this.clock.seek(time);
589
+ // Filter pending audio packets that are now too old
590
+ this.pendingAudioPackets = this.pendingAudioPackets.filter((p) => p.timestamp >= time - 0.05);
591
+ }
592
+ // Flush any buffered audio packets
593
+ if (this.pendingAudioPackets.length > 0) {
594
+ Logger.debug(TAG, `Flushing ${this.pendingAudioPackets.length} buffered audio packets after seek sync`);
595
+ for (const pkt of this.pendingAudioPackets) {
596
+ this.audioDecoder.decode(pkt.data, pkt.timestamp, pkt.keyframe);
588
597
  }
589
- if (this.pendingAudioPackets.length > 0) {
590
- Logger.debug(TAG, `Flushing ${this.pendingAudioPackets.length} buffered audio packets after video sync`);
591
- for (const pkt of this.pendingAudioPackets) {
592
- this.audioDecoder.decode(pkt.data, pkt.timestamp, pkt.keyframe);
593
- }
594
- this.pendingAudioPackets = [];
598
+ this.pendingAudioPackets = [];
599
+ }
600
+ // Transition to final state
601
+ if (this.wasPlayingBeforeSeek) {
602
+ Logger.info(TAG, "Resuming playback after seek");
603
+ this.stateManager.setState("playing");
604
+ this.clock.start();
605
+ if (!this.disableAudio && !this.audioRenderer.isAudioPlaying()) {
606
+ this.audioRenderer.play();
595
607
  }
596
608
  }
609
+ else {
610
+ Logger.info(TAG, "Seek completed in paused state");
611
+ this.stateManager.setState("ready");
612
+ // Don't start clock or audio
613
+ }
614
+ // Emit seeked event now that we are actually ready
615
+ // Convert back from media time to UI time
616
+ this.emit("seeked", Math.max(0, time - this.startTime));
597
617
  }
598
618
  /**
599
619
  * Main Playback Loop
600
620
  */
601
621
  processLoop = async () => {
602
- // Run if playing OR if we are resolving a seek (fetching target frame)
603
- if (this.stateManager.getState() !== "playing" && !this.waitingForVideoSync)
622
+ const currentState = this.stateManager.getState();
623
+ // Run if playing OR buffering (for rebuffering) OR if we are resolving a seek (fetching target frame)
624
+ if (currentState !== "playing" && currentState !== "buffering" && !this.waitingForVideoSync)
604
625
  return;
605
626
  // Capture session ID at start of loop - if a new seek starts, this loop should abort
606
627
  const currentSessionId = this.seekSessionId;
@@ -612,6 +633,29 @@ export class MoviPlayer extends EventEmitter {
612
633
  Logger.debug(TAG, "ProcessLoop aborted: new seek started");
613
634
  return;
614
635
  }
636
+ // Check if audio is rebuffering due to playback rate change
637
+ if (!this.disableAudio && this.audioRenderer.isRebuffering()) {
638
+ // Enter buffering state and pause clock until rebuffering completes
639
+ const currentState = this.stateManager.getState();
640
+ if (currentState === "playing") {
641
+ this.wasPlayingBeforeRebuffer = true;
642
+ this.stateManager.setState("buffering");
643
+ this.clock.pause();
644
+ Logger.debug(TAG, "Entered buffering state for playback rate change");
645
+ }
646
+ // Continue processing to allow new audio to be decoded and scheduled
647
+ }
648
+ else if (this.stateManager.getState() === "buffering" && this.wasPlayingBeforeRebuffer) {
649
+ // Rebuffering complete, resume playback only if we were playing before
650
+ // Transition to paused first, then let play() method handle the transition to playing
651
+ this.stateManager.setState("paused");
652
+ this.wasPlayingBeforeRebuffer = false;
653
+ Logger.debug(TAG, "Exited buffering state, resuming playback");
654
+ // Resume playback by calling play() method (proper state machine transition)
655
+ this.play().catch((err) => {
656
+ Logger.error(TAG, "Failed to resume playback after rebuffering:", err);
657
+ });
658
+ }
615
659
  // Update FileSource preload position based on current time
616
660
  if (this.source instanceof FileSource && this.mediaInfo) {
617
661
  const currentTime = this.clock.getTime();
@@ -769,6 +813,11 @@ export class MoviPlayer extends EventEmitter {
769
813
  this.seekingToKeyframe = false;
770
814
  Logger.warn(TAG, "EOF reached before finding keyframe after seek");
771
815
  }
816
+ // If we were waiting for sync, trigger it now so player doesn't hang in loading state
817
+ if (this.waitingForVideoSync) {
818
+ Logger.warn(TAG, "EOF reached while waiting for seek sync, forcing completion");
819
+ this.notifySeekCompletion(this.seekTargetTime);
820
+ }
772
821
  Logger.debug(TAG, "EOF reached");
773
822
  break;
774
823
  }
@@ -829,10 +878,9 @@ export class MoviPlayer extends EventEmitter {
829
878
  if (this.seekTargetTime >= 0 &&
830
879
  packet.timestamp >= this.seekTargetTime) {
831
880
  Logger.debug(TAG, `Audio reached seek target: ${packet.timestamp.toFixed(3)}s (target: ${this.seekTargetTime.toFixed(3)}s)`);
832
- // Only clear seek target if there is no video track to trigger it
833
- // If video exists, we wait for a valid frame in onFrame()
881
+ // If there is no video track, audio completion triggers state transition
834
882
  if (!this.trackManager.getActiveVideoTrack()) {
835
- this.seekTargetTime = -1;
883
+ this.notifySeekCompletion(packet.timestamp);
836
884
  }
837
885
  }
838
886
  this.audioDecoder.decode(packet.data, packet.timestamp, packet.keyframe);
@@ -931,6 +979,8 @@ export class MoviPlayer extends EventEmitter {
931
979
  if (currentState !== "seeking") {
932
980
  this.wasPlayingBeforeSeek = currentState === "playing";
933
981
  }
982
+ // Pause clock so UI time doesn't advance during seek while in loading state
983
+ this.clock.pause();
934
984
  const mySessionId = ++this.seekSessionId;
935
985
  this.stateManager.setState("seeking");
936
986
  this.emit("seeking", seconds);
@@ -991,57 +1041,14 @@ export class MoviPlayer extends EventEmitter {
991
1041
  this.seekTime = performance.now();
992
1042
  if (this.seekSessionId !== mySessionId)
993
1043
  return; // Superceded
994
- // Restore state based on original intent
995
- if (this.wasPlayingBeforeSeek) {
996
- // After seek, we're in 'seeking' state, which can transition to 'playing'
997
- const currentState = this.stateManager.getState();
998
- if (currentState === "seeking") {
999
- // seeking -> playing is valid
1000
- if (!this.stateManager.setState("playing")) {
1001
- Logger.warn(TAG, "Failed to transition to playing after seek, transitioning to ready first");
1002
- // Fallback: try through ready
1003
- if (this.stateManager.setState("ready")) {
1004
- this.stateManager.setState("playing");
1005
- }
1006
- }
1007
- }
1008
- else if (currentState === "ready") {
1009
- // ready -> playing is valid
1010
- this.stateManager.setState("playing");
1011
- }
1012
- else {
1013
- Logger.warn(TAG, `Cannot transition to playing from state: ${currentState} after seek`);
1014
- }
1015
- // Ensure clock is running (restores isRunning=true)
1016
- this.clock.start();
1017
- // Ensure audio engine is ready (reset() might have stopped it or cleared clock)
1018
- if (!this.disableAudio && !this.audioRenderer.isAudioPlaying()) {
1019
- await this.audioRenderer.play();
1020
- }
1021
- // IMPORTANT: Restart video presentation loop explicitly
1022
- if (this.videoRenderer) {
1023
- this.videoRenderer.startPresentationLoop();
1024
- }
1025
- if (this.animationFrameId !== null) {
1026
- cancelAnimationFrame(this.animationFrameId);
1027
- this.animationFrameId = null;
1028
- }
1029
- this.processLoop();
1030
- }
1031
- else {
1032
- // PAUSED SEEK LOGIC
1033
- // We still need to fetch and decode the frame at the new position
1034
- this.stateManager.setState("ready");
1035
- // Temporarily start processLoop to fetch the target frame
1036
- // It will stop automatically once waitingForVideoSync becomes false (see processLoop check)
1037
- this.processLoop();
1038
- // Ensure the video renderer loop is running to actually draw the frame
1039
- if (this.videoRenderer) {
1040
- this.videoRenderer.startPresentationLoop();
1041
- }
1044
+ // Start processing loop to find and decode the target frame/packet.
1045
+ // notifySeekCompletion will be called once the first valid frame is received.
1046
+ this.processLoop();
1047
+ // Ensure the video renderer loop is running to actually draw frames as they arrive
1048
+ if (this.videoRenderer) {
1049
+ this.videoRenderer.startPresentationLoop();
1042
1050
  }
1043
- this.emit("seeked", seconds);
1044
- Logger.info(TAG, `Seeked to ${seconds}s`);
1051
+ Logger.info(TAG, `Seek initiated to ${seconds}s, waiting for sync...`);
1045
1052
  }
1046
1053
  catch (error) {
1047
1054
  // Reset seeking flag on error
@@ -1902,41 +1909,16 @@ export class MoviPlayer extends EventEmitter {
1902
1909
  if (duration <= 0) {
1903
1910
  return 0;
1904
1911
  }
1905
- // For HttpSource, convert buffered bytes to time
1912
+ // For HttpSource, convert buffered bytes to time using stable linear estimation.
1913
+ // Transient bitrate-based estimation is unstable during seeks.
1906
1914
  if (this.source instanceof HttpSource && this.fileSize > 0) {
1907
1915
  const bufferedBytes = this.source.getBufferedEnd();
1908
1916
  if (bufferedBytes > 0) {
1909
- // Use current read position as reference for more accurate conversion
1910
- const currentReadPos = this.source.getPosition();
1911
- const currentTime = this.clock.getTime();
1912
- // Require minimum thresholds to ensure accurate bitrate calculation
1913
- const MIN_READ_POS = 1024 * 1024; // At least 1MB read
1914
- const MIN_TIME = 1.0; // At least 1 second of playback
1915
- // If we have a valid read position and current time, use them as a reference point
1916
- if (currentReadPos >= MIN_READ_POS &&
1917
- currentTime >= MIN_TIME &&
1918
- currentReadPos < this.fileSize) {
1919
- // Calculate effective bitrate from current position and time
1920
- const normalizedTime = Math.max(0.1, currentTime - this.startTime);
1921
- const effectiveBitrate = currentReadPos / normalizedTime; // bytes per second
1922
- if (effectiveBitrate > 0) {
1923
- // Estimate time for buffered end based on effective bitrate
1924
- const estimatedTime = bufferedBytes / effectiveBitrate;
1925
- // Clamp to valid range
1926
- return Math.max(0, Math.min(duration, estimatedTime));
1927
- }
1928
- }
1929
- // Fallback: Account for metadata overhead (first 1-2% is often metadata)
1930
- const metadataOverhead = Math.min(this.fileSize * 0.02, 2 * 1024 * 1024);
1931
- const effectiveFileSize = this.fileSize - metadataOverhead;
1932
- const effectiveBufferedBytes = Math.max(0, bufferedBytes - metadataOverhead);
1933
- if (effectiveFileSize > 0 && effectiveBufferedBytes >= 0) {
1934
- const ratio = Math.min(1, effectiveBufferedBytes / effectiveFileSize);
1935
- return ratio * duration;
1936
- }
1937
- // Last resort: simple linear
1938
1917
  const ratio = Math.min(1, bufferedBytes / this.fileSize);
1939
- return ratio * duration;
1918
+ const bufferedTime = ratio * duration;
1919
+ // Ensure buffer appears at least as far as current playback position
1920
+ // This prevents buffer bar from appearing behind progress bar
1921
+ return Math.max(bufferedTime, this.getCurrentTime());
1940
1922
  }
1941
1923
  }
1942
1924
  // For FileSource, the entire file is buffered
@@ -1983,46 +1965,13 @@ export class MoviPlayer extends EventEmitter {
1983
1965
  return 0;
1984
1966
  }
1985
1967
  const duration = this.mediaInfo.duration;
1986
- if (duration <= 0) {
1987
- return 0;
1988
- }
1989
- const bufferStartBytes = this.source.getBufferStart();
1990
- if (bufferStartBytes < 0) {
1991
- return 0;
1992
- }
1993
- // Use current read position as reference for more accurate conversion
1994
- // This accounts for metadata at the beginning (moov atom) which takes bytes but no playback time
1995
- const currentReadPos = this.source.getPosition();
1996
- const currentTime = this.clock.getTime();
1997
- // If we have a valid read position and current time, use them as a reference point
1998
- // Require minimum thresholds to ensure accurate bitrate calculation
1999
- const MIN_READ_POS = 1024 * 1024; // At least 1MB read
2000
- const MIN_TIME = 1.0; // At least 1 second of playback
2001
- if (currentReadPos >= MIN_READ_POS &&
2002
- currentTime >= MIN_TIME &&
2003
- currentReadPos < this.fileSize) {
2004
- // Calculate effective bitrate from current position and time
2005
- const effectiveBitrate = currentReadPos / currentTime; // bytes per second
2006
- if (effectiveBitrate > 0) {
2007
- // Estimate time for buffer start based on effective bitrate
2008
- // This is more accurate than linear file ratio
2009
- const estimatedTime = bufferStartBytes / effectiveBitrate;
2010
- // Clamp to valid range
2011
- return Math.max(0, Math.min(duration, estimatedTime));
2012
- }
2013
- }
2014
- // Fallback to linear estimation if we don't have a good reference point
2015
- // Account for typical metadata overhead (first 1-2% of file is often metadata)
2016
- const metadataOverhead = Math.min(this.fileSize * 0.02, 2 * 1024 * 1024); // Max 2MB or 2%
2017
- const effectiveFileSize = this.fileSize - metadataOverhead;
2018
- const effectiveStartBytes = Math.max(0, bufferStartBytes - metadataOverhead);
2019
- if (effectiveFileSize > 0 && effectiveStartBytes >= 0) {
2020
- const ratio = Math.min(1, effectiveStartBytes / effectiveFileSize);
1968
+ // For HttpSource, convert buffer start bytes to time using stable linear estimation
1969
+ if (this.source instanceof HttpSource && this.fileSize > 0) {
1970
+ const bufferStartBytes = this.source.getBufferStart();
1971
+ const ratio = Math.min(1, bufferStartBytes / this.fileSize);
2021
1972
  return ratio * duration;
2022
1973
  }
2023
- // Last resort: simple linear
2024
- const ratio = Math.min(1, bufferStartBytes / this.fileSize);
2025
- return ratio * duration;
1974
+ return 0;
2026
1975
  }
2027
1976
  /**
2028
1977
  * Get buffer end time in seconds (for HttpSource)