mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
added playback speed controls KSPlayer(iOS)
This commit is contained in:
parent
1ba0a49778
commit
a5feeb40a7
8 changed files with 95 additions and 26 deletions
|
|
@ -14,6 +14,7 @@
|
|||
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(volume, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(rate, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(audioTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(textTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL)
|
||||
|
|
@ -34,6 +35,7 @@ RCT_EXTERN_METHOD(seek:(nonnull NSNumber *)node toTime:(nonnull NSNumber *)time)
|
|||
RCT_EXTERN_METHOD(setSource:(nonnull NSNumber *)node source:(nonnull NSDictionary *)source)
|
||||
RCT_EXTERN_METHOD(setPaused:(nonnull NSNumber *)node paused:(BOOL)paused)
|
||||
RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)node volume:(nonnull NSNumber *)volume)
|
||||
RCT_EXTERN_METHOD(setPlaybackRate:(nonnull NSNumber *)node rate:(nonnull NSNumber *)rate)
|
||||
RCT_EXTERN_METHOD(setAudioTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(setTextTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(getTracks:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc var rate: NSNumber = 1.0 {
|
||||
didSet {
|
||||
setPlaybackRate(rate.floatValue)
|
||||
}
|
||||
}
|
||||
|
||||
@objc var audioTrack: NSNumber = -1 {
|
||||
didSet {
|
||||
setAudioTrack(audioTrack.intValue)
|
||||
|
|
@ -402,6 +408,11 @@ class KSPlayerView: UIView {
|
|||
playerView.playerLayer?.player.playbackVolume = volume
|
||||
}
|
||||
|
||||
func setPlaybackRate(_ rate: Float) {
|
||||
playerView.playerLayer?.player.playbackRate = rate
|
||||
print("KSPlayerView: Set playback rate to \(rate)x")
|
||||
}
|
||||
|
||||
func seek(to time: TimeInterval) {
|
||||
guard let playerLayer = playerView.playerLayer,
|
||||
playerLayer.player.isReadyToPlay,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ class KSPlayerViewManager: RCTViewManager {
|
|||
}
|
||||
}
|
||||
|
||||
@objc func setPlaybackRate(_ node: NSNumber, rate: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setPlaybackRate(Float(truncating: rate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setAudioTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface KSPlayerViewProps {
|
|||
source?: KSPlayerSource;
|
||||
paused?: boolean;
|
||||
volume?: number;
|
||||
rate?: number;
|
||||
audioTrack?: number;
|
||||
textTrack?: number;
|
||||
allowsExternalPlayback?: boolean;
|
||||
|
|
@ -34,6 +35,7 @@ export interface KSPlayerRef {
|
|||
setSource: (source: KSPlayerSource) => void;
|
||||
setPaused: (paused: boolean) => void;
|
||||
setVolume: (volume: number) => void;
|
||||
setPlaybackRate: (rate: number) => void;
|
||||
setAudioTrack: (trackId: number) => void;
|
||||
setTextTrack: (trackId: number) => void;
|
||||
getTracks: () => Promise<{ audioTracks: any[]; textTracks: any[] }>;
|
||||
|
|
@ -47,6 +49,7 @@ export interface KSPlayerProps {
|
|||
source?: KSPlayerSource;
|
||||
paused?: boolean;
|
||||
volume?: number;
|
||||
rate?: number;
|
||||
audioTrack?: number;
|
||||
textTrack?: number;
|
||||
allowsExternalPlayback?: boolean;
|
||||
|
|
@ -100,6 +103,14 @@ const KSPlayer = forwardRef<KSPlayerRef, KSPlayerProps>((props, ref) => {
|
|||
UIManager.dispatchViewManagerCommand(node, commandId, [volume]);
|
||||
}
|
||||
},
|
||||
setPlaybackRate: (rate: number) => {
|
||||
if (nativeRef.current) {
|
||||
const node = findNodeHandle(nativeRef.current);
|
||||
// @ts-ignore legacy UIManager commands path for Paper
|
||||
const commandId = UIManager.getViewManagerConfig('KSPlayerView').Commands.setPlaybackRate;
|
||||
UIManager.dispatchViewManagerCommand(node, commandId, [rate]);
|
||||
}
|
||||
},
|
||||
setAudioTrack: (trackId: number) => {
|
||||
if (nativeRef.current) {
|
||||
const node = findNodeHandle(nativeRef.current);
|
||||
|
|
@ -173,6 +184,7 @@ const KSPlayer = forwardRef<KSPlayerRef, KSPlayerProps>((props, ref) => {
|
|||
source={props.source}
|
||||
paused={props.paused}
|
||||
volume={props.volume}
|
||||
rate={props.rate}
|
||||
audioTrack={props.audioTrack}
|
||||
textTrack={props.textTrack}
|
||||
allowsExternalPlayback={props.allowsExternalPlayback}
|
||||
|
|
|
|||
|
|
@ -2730,6 +2730,7 @@ const KSPlayerCore: React.FC = () => {
|
|||
}}
|
||||
paused={paused}
|
||||
volume={volume / 100}
|
||||
rate={playbackSpeed}
|
||||
audioTrack={selectedAudioTrack ?? undefined}
|
||||
textTrack={useCustomSubtitles ? -1 : selectedTextTrack}
|
||||
allowsExternalPlayback={allowsAirPlay}
|
||||
|
|
|
|||
|
|
@ -314,9 +314,24 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
</View>
|
||||
)}
|
||||
</View>
|
||||
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
||||
<Ionicons name="close" size={closeIconSize} color="white" />
|
||||
</TouchableOpacity>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
{/* AirPlay Button - iOS only, KSAVPlayer only */}
|
||||
{Platform.OS === 'ios' && onAirPlayPress && playerBackend === 'KSAVPlayer' && (
|
||||
<TouchableOpacity
|
||||
style={{ padding: 8 }}
|
||||
onPress={onAirPlayPress}
|
||||
>
|
||||
<Feather
|
||||
name="airplay"
|
||||
size={closeIconSize}
|
||||
color={isAirPlayActive ? currentTheme.colors.primary : "white"}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<TouchableOpacity style={styles.closeButton} onPress={handleClose}>
|
||||
<Ionicons name="close" size={closeIconSize} color="white" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
|
|
@ -520,7 +535,7 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Playback Speed Button - Hidden on iOS */}
|
||||
{/* Playback Speed Button - Temporarily hidden on iOS */}
|
||||
{Platform.OS !== 'ios' && (
|
||||
<TouchableOpacity style={styles.bottomButton} onPress={cyclePlaybackSpeed}>
|
||||
<Ionicons name="speedometer" size={20} color="white" />
|
||||
|
|
@ -572,26 +587,6 @@ export const PlayerControls: React.FC<PlayerControlsProps> = ({
|
|||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* AirPlay Button - iOS only, KSAVPlayer only */}
|
||||
{Platform.OS === 'ios' && onAirPlayPress && playerBackend === 'KSAVPlayer' && (
|
||||
<TouchableOpacity
|
||||
style={styles.bottomButton}
|
||||
onPress={onAirPlayPress}
|
||||
>
|
||||
<Feather
|
||||
name="airplay"
|
||||
size={20}
|
||||
color={isAirPlayActive ? currentTheme.colors.primary : "white"}
|
||||
/>
|
||||
<Text style={[
|
||||
styles.bottomButtonText,
|
||||
isAirPlayActive && { color: currentTheme.colors.primary }
|
||||
]}>
|
||||
{allowsAirPlay ? 'AirPlay' : 'AirPlay Off'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
|
|
|||
|
|
@ -131,6 +131,14 @@ export const styles = StyleSheet.create({
|
|||
closeButton: {
|
||||
padding: 8,
|
||||
},
|
||||
topRightButtons: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
topButton: {
|
||||
padding: 8,
|
||||
},
|
||||
|
||||
|
||||
/* CloudStream Style - Center Controls */
|
||||
|
|
|
|||
|
|
@ -18,7 +18,22 @@ export interface BackupData {
|
|||
watchProgress: Record<string, any>;
|
||||
addons: any[];
|
||||
downloads: DownloadItem[];
|
||||
subtitles: any;
|
||||
subtitles: {
|
||||
subtitleSize?: number;
|
||||
subtitleBackground?: boolean;
|
||||
subtitleTextColor?: string;
|
||||
subtitleBgOpacity?: number;
|
||||
subtitleTextShadow?: boolean;
|
||||
subtitleOutline?: boolean;
|
||||
subtitleOutlineColor?: string;
|
||||
subtitleOutlineWidth?: number;
|
||||
subtitleAlign?: 'center' | 'left' | 'right';
|
||||
subtitleBottomOffset?: number;
|
||||
subtitleLetterSpacing?: number;
|
||||
subtitleLineHeightMultiplier?: number;
|
||||
subtitleOffsetSec?: number;
|
||||
[key: string]: any; // Allow for additional subtitle preferences
|
||||
};
|
||||
tombstones: Record<string, number>;
|
||||
continueWatchingRemoved: Record<string, number>;
|
||||
contentDuration: Record<string, number>;
|
||||
|
|
@ -444,7 +459,18 @@ export class BackupService {
|
|||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@subtitle_settings`;
|
||||
const subtitlesJson = await AsyncStorage.getItem(scopedKey);
|
||||
return subtitlesJson ? JSON.parse(subtitlesJson) : {};
|
||||
let subtitleSettings = subtitlesJson ? JSON.parse(subtitlesJson) : {};
|
||||
|
||||
// Also check for legacy subtitle size preference
|
||||
const legacySubtitleSize = await AsyncStorage.getItem('@subtitle_size_preference');
|
||||
if (legacySubtitleSize && !subtitleSettings.subtitleSize) {
|
||||
const legacySize = parseInt(legacySubtitleSize, 10);
|
||||
if (!Number.isNaN(legacySize) && legacySize > 0) {
|
||||
subtitleSettings.subtitleSize = legacySize;
|
||||
}
|
||||
}
|
||||
|
||||
return subtitleSettings;
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to get subtitle settings:', error);
|
||||
return {};
|
||||
|
|
@ -738,6 +764,12 @@ export class BackupService {
|
|||
const scope = await this.getUserScope();
|
||||
const scopedKey = `@user:${scope}:@subtitle_settings`;
|
||||
await AsyncStorage.setItem(scopedKey, JSON.stringify(subtitles));
|
||||
|
||||
// Also restore legacy subtitle size preference for backward compatibility
|
||||
if (subtitles && typeof subtitles.subtitleSize === 'number') {
|
||||
await AsyncStorage.setItem('@subtitle_size_preference', subtitles.subtitleSize.toString());
|
||||
}
|
||||
|
||||
logger.info('[BackupService] Subtitle settings restored');
|
||||
} catch (error) {
|
||||
logger.error('[BackupService] Failed to restore subtitle settings:', error);
|
||||
|
|
|
|||
Loading…
Reference in a new issue