diff --git a/app/_layout.tsx b/app/_layout.tsx
index f592f51..700de07 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -4,6 +4,7 @@ import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { Platform } from "react-native";
+import Toast from "react-native-toast-message";
import { useSettingsStore } from "@/stores/settingsStore";
@@ -43,6 +44,7 @@ export default function RootLayout() {
+
);
}
diff --git a/app/play.tsx b/app/play.tsx
index 94e13b2..655068b 100644
--- a/app/play.tsx
+++ b/app/play.tsx
@@ -34,6 +34,7 @@ export default function PlayScreen() {
showSourceModal,
showNextEpisodeOverlay,
initialPosition,
+ introEndTime,
setVideoRef,
loadVideo,
playEpisode,
@@ -102,8 +103,9 @@ export default function PlayScreen() {
resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onLoad={() => {
- if (initialPosition > 0) {
- videoRef.current?.setPositionAsync(initialPosition);
+ const jumpPosition = introEndTime || initialPosition;
+ if (jumpPosition > 0) {
+ videoRef.current?.setPositionAsync(jumpPosition);
}
usePlayerStore.setState({ isLoading: false });
}}
diff --git a/components/MediaButton.tsx b/components/MediaButton.tsx
index 6f67c68..5eb13f6 100644
--- a/components/MediaButton.tsx
+++ b/components/MediaButton.tsx
@@ -1,11 +1,16 @@
import React, { ComponentProps } from "react";
import { StyledButton } from "./StyledButton";
-import { StyleSheet } from "react-native";
+import { StyleSheet, View, Text } from "react-native";
-type StyledButtonProps = ComponentProps;
+type StyledButtonProps = ComponentProps & {
+ timeLabel?: string;
+};
-export const MediaButton = (props: StyledButtonProps) => (
-
+export const MediaButton = ({ timeLabel, ...props }: StyledButtonProps) => (
+
+
+ {timeLabel && {timeLabel}}
+
);
const styles = StyleSheet.create({
@@ -13,4 +18,15 @@ const styles = StyleSheet.create({
padding: 12,
minWidth: 80,
},
+ timeLabel: {
+ position: "absolute",
+ top: 14,
+ right: 12,
+ color: "white",
+ fontSize: 10,
+ fontWeight: "bold",
+ backgroundColor: "rgba(0,0,0,0.6)",
+ paddingHorizontal: 4,
+ borderRadius: 3,
+ },
});
diff --git a/components/PlayerControls.tsx b/components/PlayerControls.tsx
index 90f25c1..180ff10 100644
--- a/components/PlayerControls.tsx
+++ b/components/PlayerControls.tsx
@@ -2,7 +2,17 @@ import React, { useCallback, useState } from "react";
import { View, Text, StyleSheet, TouchableOpacity, Pressable } from "react-native";
import { useRouter } from "expo-router";
import { AVPlaybackStatus } from "expo-av";
-import { ArrowLeft, Pause, Play, SkipForward, List, ChevronsRight, ChevronsLeft, Tv } from "lucide-react-native";
+import {
+ Pause,
+ Play,
+ SkipForward,
+ List,
+ ChevronsRight,
+ ChevronsLeft,
+ Tv,
+ ArrowDownToDot,
+ ArrowUpFromDot,
+} from "lucide-react-native";
import { ThemedText } from "@/components/ThemedText";
import { MediaButton } from "@/components/MediaButton";
@@ -28,6 +38,10 @@ export const PlayerControls: React.FC = ({ showControls, se
playEpisode,
setShowEpisodeModal,
setShowSourceModal,
+ setIntroEndTime,
+ setOutroStartTime,
+ introEndTime,
+ outroStartTime,
} = usePlayerStore();
const videoTitle = detail?.videoInfo?.title || "";
@@ -81,8 +95,8 @@ export const PlayerControls: React.FC = ({ showControls, se
- seek(-15000)}>
-
+
+
@@ -97,8 +111,8 @@ export const PlayerControls: React.FC = ({ showControls, se
- seek(15000)}>
-
+
+
setShowEpisodeModal(true)}>
diff --git a/hooks/useTVRemoteHandler.ts b/hooks/useTVRemoteHandler.ts
index d3c942f..a5e7c36 100644
--- a/hooks/useTVRemoteHandler.ts
+++ b/hooks/useTVRemoteHandler.ts
@@ -67,7 +67,6 @@ export const useTVRemoteHandler = () => {
if (event.eventType === "longRight" || event.eventType === "longLeft") {
if (event.eventKeyAction === 1) {
if (fastForwardIntervalRef.current) {
- console.log("Long right key released, stopping fast forward.");
clearInterval(fastForwardIntervalRef.current);
fastForwardIntervalRef.current = null;
}
@@ -82,8 +81,6 @@ export const useTVRemoteHandler = () => {
return;
}
- console.log("TV Event:", event);
-
switch (event.eventType) {
case "select":
togglePlayPause();
diff --git a/package.json b/package.json
index 866676e..c5210eb 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "3.31.1",
"react-native-svg": "^15.12.0",
+ "react-native-toast-message": "^2.3.3",
"react-native-web": "~0.19.10",
"zustand": "^5.0.6"
},
diff --git a/services/storage.ts b/services/storage.ts
index 91789b8..c1f3df7 100644
--- a/services/storage.ts
+++ b/services/storage.ts
@@ -10,7 +10,10 @@ const STORAGE_KEYS = {
} as const;
// --- Type Definitions (aligned with api.ts) ---
-export type PlayRecord = ApiPlayRecord;
+export interface PlayRecord extends ApiPlayRecord {
+ introEndTime?: number;
+ outroStartTime?: number;
+}
export interface FavoriteItem {
id: string;
diff --git a/stores/playerStore.ts b/stores/playerStore.ts
index c3fc641..6793017 100644
--- a/stores/playerStore.ts
+++ b/stores/playerStore.ts
@@ -1,8 +1,9 @@
import { create } from "zustand";
+import Toast from "react-native-toast-message";
import { AVPlaybackStatus, Video } from "expo-av";
import { RefObject } from "react";
import { api, VideoDetail as ApiVideoDetail, SearchResult } from "@/services/api";
-import { PlayRecordManager } from "@/services/storage";
+import { PlayRecord, PlayRecordManager } from "@/services/storage";
interface Episode {
url: string;
@@ -32,6 +33,8 @@ interface PlayerState {
seekPosition: number;
progressPosition: number;
initialPosition: number;
+ introEndTime?: number;
+ outroStartTime?: number;
setVideoRef: (ref: RefObject