mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
Used debouncing for trick play and stop rendering trickplay, while not sliding
This commit is contained in:
@@ -52,6 +52,7 @@ import DropdownViewDirect from "./dropdown/DropdownViewDirect";
|
||||
import DropdownViewTranscoding from "./dropdown/DropdownViewTranscoding";
|
||||
import BrightnessSlider from "./BrightnessSlider";
|
||||
import SkipButton from "./SkipButton";
|
||||
import { debounce } from "lodash";
|
||||
|
||||
interface Props {
|
||||
item: BaseItemDto;
|
||||
@@ -245,13 +246,25 @@ export const Controls: React.FC<Props> = ({
|
||||
useEffect(() => {
|
||||
prefetchAllTrickplayImages();
|
||||
}, []);
|
||||
|
||||
const toggleControls = () => setShowControls(!showControls);
|
||||
|
||||
const handleSliderStart = useCallback(() => {
|
||||
if (showControls === false) return;
|
||||
|
||||
setIsSliding(true);
|
||||
wasPlayingRef.current = isPlaying;
|
||||
lastProgressRef.current = progress.value;
|
||||
|
||||
pause();
|
||||
isSeeking.value = true;
|
||||
}, [showControls, isPlaying]);
|
||||
|
||||
const [isSliding, setIsSliding] = useState(false);
|
||||
const handleSliderComplete = useCallback(
|
||||
async (value: number) => {
|
||||
isSeeking.value = false;
|
||||
progress.value = value;
|
||||
setIsSliding(false);
|
||||
|
||||
await seek(
|
||||
Math.max(0, Math.floor(isVlc ? value : ticksToSeconds(value)))
|
||||
@@ -262,27 +275,20 @@ export const Controls: React.FC<Props> = ({
|
||||
);
|
||||
|
||||
const [time, setTime] = useState({ hours: 0, minutes: 0, seconds: 0 });
|
||||
const handleSliderChange = useCallback(
|
||||
debounce((value: number) => {
|
||||
const progressInTicks = msToTicks(value);
|
||||
console.log("Progress in ticks", progressInTicks);
|
||||
calculateTrickplayUrl(progressInTicks);
|
||||
|
||||
const handleSliderChange = (value: number) => {
|
||||
const progressInTicks = isVlc ? msToTicks(value) : value;
|
||||
calculateTrickplayUrl(progressInTicks);
|
||||
|
||||
const progressInSeconds = Math.floor(ticksToSeconds(progressInTicks));
|
||||
const hours = Math.floor(progressInSeconds / 3600);
|
||||
const minutes = Math.floor((progressInSeconds % 3600) / 60);
|
||||
const seconds = progressInSeconds % 60;
|
||||
setTime({ hours, minutes, seconds });
|
||||
};
|
||||
|
||||
const handleSliderStart = useCallback(() => {
|
||||
if (showControls === false) return;
|
||||
|
||||
wasPlayingRef.current = isPlaying;
|
||||
lastProgressRef.current = progress.value;
|
||||
|
||||
pause();
|
||||
isSeeking.value = true;
|
||||
}, [showControls, isPlaying]);
|
||||
const progressInSeconds = Math.floor(ticksToSeconds(progressInTicks));
|
||||
const hours = Math.floor(progressInSeconds / 3600);
|
||||
const minutes = Math.floor((progressInSeconds % 3600) / 60);
|
||||
const seconds = progressInSeconds % 60;
|
||||
setTime({ hours, minutes, seconds });
|
||||
}, 20), // 100ms debounce delay
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSkipBackward = useCallback(async () => {
|
||||
if (!settings?.rewindSkipTime) return;
|
||||
@@ -326,6 +332,71 @@ export const Controls: React.FC<Props> = ({
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
}, []);
|
||||
|
||||
const memoizedRenderBubble = useCallback(() => {
|
||||
if (!trickPlayUrl || !trickplayInfo) {
|
||||
return null;
|
||||
}
|
||||
const { x, y, url } = trickPlayUrl;
|
||||
const tileWidth = 150;
|
||||
const tileHeight = 150 / trickplayInfo.aspectRatio!;
|
||||
|
||||
console.log("time, ", time);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: -57,
|
||||
bottom: 15,
|
||||
paddingTop: 30,
|
||||
paddingBottom: 5,
|
||||
width: tileWidth * 1.5,
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
alignSelf: "center",
|
||||
transform: [{ scale: 1.4 }],
|
||||
borderRadius: 5,
|
||||
}}
|
||||
className="bg-neutral-800 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
cachePolicy={"memory-disk"}
|
||||
style={{
|
||||
width: 150 * trickplayInfo?.data.TileWidth!,
|
||||
height:
|
||||
(150 / trickplayInfo.aspectRatio!) *
|
||||
trickplayInfo?.data.TileHeight!,
|
||||
transform: [
|
||||
{ translateX: -x * tileWidth },
|
||||
{ translateY: -y * tileHeight },
|
||||
],
|
||||
resizeMode: "cover",
|
||||
}}
|
||||
source={{ uri: url }}
|
||||
contentFit="cover"
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 30,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{`${time.hours > 0 ? `${time.hours}:` : ""}${
|
||||
time.minutes < 10 ? `0${time.minutes}` : time.minutes
|
||||
}:${time.seconds < 10 ? `0${time.seconds}` : time.seconds}`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}, [trickPlayUrl, trickplayInfo, time]);
|
||||
|
||||
return (
|
||||
<ControlProvider
|
||||
item={item}
|
||||
@@ -594,69 +665,7 @@ export const Controls: React.FC<Props> = ({
|
||||
containerStyle={{
|
||||
borderRadius: 100,
|
||||
}}
|
||||
renderBubble={() => {
|
||||
if (!trickPlayUrl || !trickplayInfo) {
|
||||
return null;
|
||||
}
|
||||
const { x, y, url } = trickPlayUrl;
|
||||
const tileWidth = 150;
|
||||
const tileHeight = 150 / trickplayInfo.aspectRatio!;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: -57,
|
||||
bottom: 15,
|
||||
paddingTop: 30,
|
||||
paddingBottom: 5,
|
||||
width: tileWidth * 1.5, // Adjust the width of the outer container if needed
|
||||
backgroundColor: "rgba(0, 0, 0, 0.6)", // Outer box background color (optional)
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
alignSelf: "center",
|
||||
transform: [{ scale: 1.4 }],
|
||||
borderRadius: 5, // Optional border radius
|
||||
}}
|
||||
className=" bg-neutral-800 overflow-hidden"
|
||||
>
|
||||
<Image
|
||||
cachePolicy={"memory-disk"}
|
||||
style={{
|
||||
width: 150 * trickplayInfo?.data.TileWidth!,
|
||||
height:
|
||||
(150 / trickplayInfo.aspectRatio!) *
|
||||
trickplayInfo?.data.TileHeight!,
|
||||
transform: [
|
||||
{ translateX: -x * tileWidth },
|
||||
{ translateY: -y * tileHeight },
|
||||
],
|
||||
resizeMode: "cover",
|
||||
}}
|
||||
source={{ uri: url }}
|
||||
contentFit="cover"
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
marginTop: 30,
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
{`${time.hours > 0 ? `${time.hours}:` : ""}${
|
||||
time.minutes < 10 ? `0${time.minutes}` : time.minutes
|
||||
}:${
|
||||
time.seconds < 10 ? `0${time.seconds}` : time.seconds
|
||||
}`}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
renderBubble={() => isSliding && memoizedRenderBubble()}
|
||||
sliderHeight={10}
|
||||
thumbWidth={0}
|
||||
progress={progress}
|
||||
|
||||
@@ -59,46 +59,37 @@ class VlcPlayerView: ExpoView {
|
||||
// MARK: - Public Methods
|
||||
|
||||
@objc func play() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mediaPlayer?.play()
|
||||
self?.isPaused = false
|
||||
print("Play")
|
||||
}
|
||||
self.mediaPlayer?.play()
|
||||
self.isPaused = false
|
||||
print("Play")
|
||||
}
|
||||
|
||||
@objc func pause() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mediaPlayer?.pause()
|
||||
self?.isPaused = true
|
||||
}
|
||||
self.mediaPlayer?.pause()
|
||||
self.isPaused = true
|
||||
}
|
||||
|
||||
@objc func seekTo(_ time: Int32) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self, let player = self.mediaPlayer else { return }
|
||||
guard let player = self.mediaPlayer else { return }
|
||||
|
||||
let wasPlaying = player.isPlaying
|
||||
if wasPlaying {
|
||||
self.pause()
|
||||
}
|
||||
|
||||
if let duration = player.media?.length.intValue {
|
||||
print("Seeking to time: \(time) Video Duration \(duration)")
|
||||
|
||||
// If the specified time is greater than the duration, seek to the end
|
||||
let seekTime = time > duration ? duration - 1000 : time
|
||||
player.time = VLCTime(int: seekTime)
|
||||
|
||||
let wasPlaying = player.isPlaying
|
||||
if wasPlaying {
|
||||
self.pause()
|
||||
}
|
||||
|
||||
if let duration = player.media?.length.intValue {
|
||||
print("Seeking to time: \(time) Video Duration \(duration)")
|
||||
|
||||
// If the specified time is greater than the duration, seek to the end
|
||||
let seekTime = time > duration ? duration - 1000 : time
|
||||
player.time = VLCTime(int: seekTime)
|
||||
|
||||
// Wait for a short moment to ensure the seek has been processed
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
if wasPlaying {
|
||||
self.play()
|
||||
}
|
||||
self.updatePlayerState()
|
||||
}
|
||||
} else {
|
||||
print("Error: Unable to retrieve video duration")
|
||||
self.play()
|
||||
}
|
||||
self.updatePlayerState()
|
||||
} else {
|
||||
print("Error: Unable to retrieve video duration")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +137,6 @@ class VlcPlayerView: ExpoView {
|
||||
print("Debug: Media options: \(mediaOptions)")
|
||||
media.addOptions(mediaOptions)
|
||||
|
||||
// Apply subtitle options
|
||||
let subtitleTrackIndex = source["subtitleTrackIndex"] as? Int ?? -1
|
||||
print("Debug: Subtitle track index from source: \(subtitleTrackIndex)")
|
||||
self.setSubtitleTrack(subtitleTrackIndex)
|
||||
|
||||
self.mediaPlayer?.media = media
|
||||
self.hasSource = true
|
||||
|
||||
@@ -162,9 +148,7 @@ class VlcPlayerView: ExpoView {
|
||||
}
|
||||
|
||||
@objc func setAudioTrack(_ trackIndex: Int) {
|
||||
DispatchQueue.main.async {
|
||||
self.mediaPlayer?.currentAudioTrackIndex = Int32(trackIndex)
|
||||
}
|
||||
self.mediaPlayer?.currentAudioTrackIndex = Int32(trackIndex)
|
||||
}
|
||||
|
||||
@objc func getAudioTracks() -> [[String: Any]]? {
|
||||
@@ -181,29 +165,25 @@ class VlcPlayerView: ExpoView {
|
||||
|
||||
@objc func setSubtitleTrack(_ trackIndex: Int) {
|
||||
print("Debug: Attempting to set subtitle track to index: \(trackIndex)")
|
||||
DispatchQueue.main.async {
|
||||
self.mediaPlayer?.currentVideoSubTitleIndex = Int32(trackIndex)
|
||||
print(
|
||||
"Debug: Current subtitle track index after setting: \(self.mediaPlayer?.currentVideoSubTitleIndex ?? -1)"
|
||||
)
|
||||
}
|
||||
self.mediaPlayer?.currentVideoSubTitleIndex = Int32(trackIndex)
|
||||
print(
|
||||
"Debug: Current subtitle track index after setting: \(self.mediaPlayer?.currentVideoSubTitleIndex ?? -1)"
|
||||
)
|
||||
}
|
||||
|
||||
@objc func setSubtitleURL(_ subtitleURL: String, name: String) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self, let url = URL(string: subtitleURL) else {
|
||||
print("Error: Invalid subtitle URL")
|
||||
return
|
||||
}
|
||||
guard let url = URL(string: subtitleURL) else {
|
||||
print("Error: Invalid subtitle URL")
|
||||
return
|
||||
}
|
||||
|
||||
let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true)
|
||||
if let result = result {
|
||||
let internalName = "Track \(self.customSubtitles.count + 1)"
|
||||
print("Subtitle added with result: \(result) \(internalName)")
|
||||
self.customSubtitles.append((internalName: internalName, originalName: name))
|
||||
} else {
|
||||
print("Failed to add subtitle")
|
||||
}
|
||||
let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true)
|
||||
if let result = result {
|
||||
let internalName = "Track \(self.customSubtitles.count + 1)"
|
||||
print("Subtitle added with result: \(result) \(internalName)")
|
||||
self.customSubtitles.append((internalName: internalName, originalName: name))
|
||||
} else {
|
||||
print("Failed to add subtitle")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user