How to Resume Main Video Playback from Saved Position?

I have a piece of code where I want to insert a mid-roll ad video. After the selected main video has played halfway, I want to start playing a second video as an advertisement.
The problem I’m facing is that when I skip or wait until the second video (the ad) finishes, the main video restarts from the beginning instead of continuing from where it left off.
Is there a way to set the playback time for the main video so that it resumes from the saved position after skipping or finishing the ad?
I tried player.currentTime = mainPosition;, but it didn’t work
Here my code

import React, {useRef, useEffect, useState} from 'react';
import {
  StyleSheet,
  View,
  Text,
  TouchableOpacity,
  ActivityIndicator,
  BackHandler,
} from 'react-native';
import {useNavigation} from '@amazon-devices/react-navigation__native';
import {VideoPlayer} from '@amazon-devices/react-native-w3cmedia';
import {useAuthStore} from '../../store/home';
import VideoSurface from './VideoSurface';
import ProgressBar from './ProgressBar';

export default function PlayerScreen() {
  const navigation = useNavigation();
  const {videoSelect} = useAuthStore();
  const videoPlayer = useRef<VideoPlayer | null>(null);

  // Player states
  const [isReady, setIsReady] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [surfaceReady, setSurfaceReady] = useState(false);
  const [isPlaying, setIsPlaying] = useState(false);
  const [progress, setProgress] = useState(0);
  const [duration, setDuration] = useState(0);

  // Ad-related states
  const [isAdPlaying, setIsAdPlaying] = useState(false);
  const adUrl = 'https://cdn.pixabay.com/video/2015/08/08/125-135736646_large.mp4';
  const [adSkipped, setAdSkipped] = useState(false);
  const [mainUrl, setMainUrl] = useState('');
  const [mainPosition, setMainPosition] = useState(0);

  // Cleanup player on mount
  useEffect(() => {
    cleanup();
  }, []);

  // Initialize player
  useEffect(() => {
    if (!isReady) initVideoPlayer();
  }, [isReady]);

  // Start main video when surface and player are ready
  useEffect(() => {
    if (surfaceReady && videoPlayer.current && !isAdPlaying) startPlayback();
  }, [surfaceReady, isAdPlaying]);

  // Handle play/pause state changes
  useEffect(() => {
    const player = videoPlayer.current;
    if (!player) return;
    const onPlaying = () => setIsPlaying(true);
    const onPaused = () => setIsPlaying(false);
    player.addEventListener('playing', onPlaying);
    player.addEventListener('pause', onPaused);
    return () => {
      player.removeEventListener('playing', onPlaying);
      player.removeEventListener('pause', onPaused);
    };
  }, [videoPlayer.current]);

  // Handle back button on TV remote
  useEffect(() => {
    const onBackPress = () => {
      handleBackPress();
      return true;
    };
    const backListener = BackHandler.addEventListener('hardwareBackPress', onBackPress);
    return () => backListener.remove();
  }, []);

  // Update progress every second
  useEffect(() => {
    let mounted = true;
    const interval = setInterval(async () => {
      const player = videoPlayer.current;
      if (!player || !mounted || isAdPlaying) return;
      try {
        const current = player.currentTime || 0;
        let total = player.duration || 0;
        if ((!total || total < current) && videoSelect?.duration)
          total = videoSelect.duration;
        setProgress(current);
        setDuration((prev) => (total > prev ? total : prev));
      } catch (err) {
        console.warn('[Progress update error]', err);
      }
    }, 1000);
    return () => {
      mounted = false;
      clearInterval(interval);
    };
  }, [videoSelect, isAdPlaying]);

  // Auto-play ad at halfway point
  useEffect(() => {
    if (isAdPlaying || adSkipped) return;
    if (duration > 0 && progress >= duration / 2) {
      playAdVideo();
    }
  }, [progress, duration]);

  // Create and initialize VideoPlayer instance
  const initVideoPlayer = async () => {
    if (videoPlayer.current) return;
    try {
      setIsLoading(true);
      videoPlayer.current = new VideoPlayer();
      await videoPlayer.current.initialize();
      videoPlayer.current.autoplay = false;
      setIsReady(true);

      // Update duration when available
      videoPlayer.current.addEventListener('durationchange', async () => {
        const total = await videoPlayer.current?.duration;
        if (typeof total === 'number' && total > 0) setDuration(total);
      });
    } catch (err) {
      console.error('[Init error]', err);
    } finally {
      setIsLoading(false);
    }
  };

  // Start main video playback
  const startPlayback = async () => {
    try {
      setIsLoading(true);
      const player = videoPlayer.current;
      if (!player) return;

      const url = videoSelect.videos?.small?.url;
      setMainUrl(url);
      player.src = url;

      // Load video (non-blocking)
      if (typeof player.load === 'function') {
        try {
          await player.load();
        } catch (e) {
          console.warn('load() failed (non-fatal)', e);
        }
      }

      // Play video
      await player.play();
      setIsPlaying(true);
      setProgress(player.currentTime || 0);
    } catch (err) {
      console.error('[Playback error]', err);
    } finally {
      setIsLoading(false);
    }
  };

  // Pause main video and play ad video
  const playAdVideo = async () => {
    const player = videoPlayer.current;
    if (!player) return;

    try {
      const currentPosition = player.currentTime || 0;
      setMainPosition(currentPosition); // Save current position
      setIsAdPlaying(true);
      await player.pause();

      player.src = adUrl;
      await player.load();
      await player.play();

      // Resume main video after ad ends
      player.addEventListener('ended', async function onAdEnded() {
        player.removeEventListener('ended', onAdEnded);
        await resumeMainVideo();
      });
    } catch (err) {
      console.error('[Ad playback error]', err);
      setIsAdPlaying(false);
    }
  };

  // Allow user to skip ad manually
  const skipAd = async () => {
    const player = videoPlayer.current;
    if (!player) return;

    try {
      await player.pause();
      setAdSkipped(true);
      await resumeMainVideo();
    } catch (err) {
      console.error('[Skip ad error]', err);
    }
  };

  // Resume main video from saved position
  const resumeMainVideo = async () => {
    const player = videoPlayer.current;
    if (!player) return;

    try {
      setIsAdPlaying(false);

      // Reload main video if needed
      if (player.src !== mainUrl) {
        player.src = mainUrl;
        await player.load();
      }

      // Seek to previous position
      player.currentTime = mainPosition;
      await player.play();
    } catch (err) {
      console.error('[Resume main video error]', err);
    }
  };

  // Stop and release player resources
  const cleanup = async () => {
    const player = videoPlayer.current;
    videoPlayer.current = null;
    if (!player) return;
    try {
      await player.pause();
      await player.deinitialize();
    } catch (err) {
      console.warn('[Cleanup error]', err);
    }
  };

  // Handle Back button action
  const handleBackPress = async () => {
    try {
      await videoPlayer.current?.pause();
    } catch {}
    cleanup();
    navigation.goBack();
  };

  return (
    <View style={styles.container}>
      {isReady ? (
        <>
          {/* Video rendering surface */}
          <VideoSurface playerRef={videoPlayer} setSurfaceReady={setSurfaceReady} />

          {/* Back button */}
          <TouchableOpacity style={styles.backButton} onPress={handleBackPress}>
            <Text style={styles.backLabel}>← Back</Text>
          </TouchableOpacity>

          {/* Progress bar for main video */}
          {!isAdPlaying && duration > 0 && (
            <ProgressBar
              progress={progress}
              duration={duration}
              playerRef={videoPlayer}
              isPlaying={isPlaying}
              setIsPlaying={setIsPlaying}
            />
          )}

          {/* Ad overlay with skip button */}
          {isAdPlaying && (
            <View style={styles.adOverlay}>
              <Text style={styles.adLabel}>Advertisement</Text>
              <TouchableOpacity style={styles.skipButton} onPress={skipAd}>
                <Text style={styles.skipLabel}>Skip Ad</Text>
              </TouchableOpacity>
            </View>
          )}
        </>
      ) : (
        // Initial start button
        <TouchableOpacity
          style={styles.playButton}
          onPress={initVideoPlayer}
          hasTVPreferredFocus={true}>
          <Text style={styles.playLabel}>Start video</Text>
        </TouchableOpacity>
      )}

      {/* Loading indicator */}
      {isLoading && (
        <View style={styles.overlay}>
          <ActivityIndicator size="large" color="#fff" />
          <Text style={styles.loadingText}>Loading video...</Text>
        </View>
      )}
    </View>
  );
}

// --- Styles ---
const styles = StyleSheet.create({
  container: {flex: 1, backgroundColor: 'black'},
  playButton: {
    backgroundColor: '#303030',
    padding: 20,
    borderRadius: 10,
    alignSelf: 'center',
  },
  playLabel: {color: '#fff', fontSize: 20},
  overlay: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0,0,0,0.3)',
  },
  loadingText: {color: 'white', marginTop: 10, fontSize: 18},
  backButton: {
    position: 'absolute',
    top: 40,
    left: 30,
    backgroundColor: 'rgba(0,0,0,0.5)',
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 8,
    zIndex: 20,
  },
  backLabel: {color: 'white', fontSize: 30},
  adOverlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0,0,0,0.3)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  adLabel: {color: 'white', fontSize: 24, marginBottom: 20},
  skipButton: {
    backgroundColor: '#ff4444',
    paddingHorizontal: 25,
    paddingVertical: 10,
    borderRadius: 8,
  },
  skipLabel: {color: 'white', fontSize: 18},
});

Thanks you!

Hi @Hoang_Dang

The issue seems to be in your resumeMainVideo function. You need to wait for the video to load before setting currentTime

Here’s my suggestion:

// Resume main video from saved position
const resumeMainVideo = async () => {
  const player = videoPlayer.current;
  if (!player) return;

  try {
    setIsAdPlaying(false);

    // Reload main video if needed
    if (player.src !== mainUrl) {
      player.src = mainUrl;
      
      // Wait for video to load before seeking
      await new Promise((resolve) => {
        const onLoadedMetadata = () => {
          player.removeEventListener('loadedmetadata', onLoadedMetadata);
          resolve();
        };
        player.addEventListener('loadedmetadata', onLoadedMetadata);
        player.load();
      });
    }

    // Now seek to previous position
    player.currentTime = mainPosition;
    
    // Wait for seek to complete before playing
    await new Promise((resolve) => {
      const onSeeked = () => {
        player.removeEventListener('seeked', onSeeked);
        resolve();
      };
      player.addEventListener('seeked', onSeeked);
    });
    
    await player.play();
  } catch (err) {
    console.error('[Resume main video error]', err);
  }
};

Key changes:

  1. Wait for loadedmetadata event after load() before setting currentTime
  2. Wait for seeked event after setting currentTime before calling play()

This will ensure the video is properly loaded and seeked before resuming playback.
Please try and let me know if this approach works for you.

Warm regards,
Ivy

Hi Ivy_Mahajan,
Can you check my code to see why the total duration of some videos is incorrect?
In the same code, some videos can load the exact duration. Still, many videos initially display a total duration of 5 seconds — then, when the playback reaches 5/5, the video suddenly jumps and shows 5/7, continuing to play.

Regards,
Hoang Dang

Hi @Hoang_Dang

The reported behavior suggests progressive loading - the video player initially only knows about the first 5 seconds of buffered content, then discovers more as it downloads.
The issue might be that some video formats/servers don’t provide accurate duration metadata upfront. The durationchange event should fire when the player gets better duration info as it loads more content.

You can try adding console.log to see when/how the duration actually changes and correct your code accordingly.

Warm regards,
Ivy