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!