mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-01-11 20:10:25 +00:00
sub changes
This commit is contained in:
parent
91af3a4021
commit
a89c7f5c5c
8 changed files with 554 additions and 402 deletions
|
|
@ -5,11 +5,11 @@
|
|||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTViewManager.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerViewManager, RCTViewManager)
|
||||
@interface RCT_EXTERN_MODULE (KSPlayerViewManager, RCTViewManager)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL)
|
||||
|
|
@ -21,6 +21,8 @@ RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL)
|
|||
RCT_EXPORT_VIEW_PROPERTY(usesExternalPlaybackWhileExternalScreenIsActive, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleBottomOffset, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleFontSize, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleTextColor, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleBackgroundColor, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString)
|
||||
|
||||
// Event properties
|
||||
|
|
@ -31,25 +33,37 @@ RCT_EXPORT_VIEW_PROPERTY(onEnd, RCTDirectEventBlock)
|
|||
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onBufferingProgress, RCTDirectEventBlock)
|
||||
|
||||
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)
|
||||
RCT_EXTERN_METHOD(setAllowsExternalPlayback:(nonnull NSNumber *)node allows:(BOOL)allows)
|
||||
RCT_EXTERN_METHOD(setUsesExternalPlaybackWhileExternalScreenIsActive:(nonnull NSNumber *)node uses:(BOOL)uses)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(nonnull NSNumber *)node)
|
||||
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)
|
||||
RCT_EXTERN_METHOD(setAllowsExternalPlayback : (nonnull NSNumber *)
|
||||
node allows : (BOOL)allows)
|
||||
RCT_EXTERN_METHOD(setUsesExternalPlaybackWhileExternalScreenIsActive : (
|
||||
nonnull NSNumber *)node uses : (BOOL)uses)
|
||||
RCT_EXTERN_METHOD(getAirPlayState : (nonnull NSNumber *)node resolve : (
|
||||
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker : (nonnull NSNumber *)node)
|
||||
|
||||
@end
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerModule, RCTEventEmitter)
|
||||
@interface RCT_EXTERN_MODULE (KSPlayerModule, RCTEventEmitter)
|
||||
|
||||
RCT_EXTERN_METHOD(getTracks:(NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(NSNumber *)nodeTag)
|
||||
RCT_EXTERN_METHOD(getTracks : (NSNumber *)nodeTag resolve : (
|
||||
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(getAirPlayState : (NSNumber *)nodeTag resolve : (
|
||||
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker : (NSNumber *)nodeTag)
|
||||
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import Foundation
|
|||
import KSPlayer
|
||||
import React
|
||||
import AVKit
|
||||
import SwiftUI
|
||||
|
||||
@objc(KSPlayerView)
|
||||
class KSPlayerView: UIView {
|
||||
|
|
@ -98,6 +99,20 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc var subtitleTextColor: NSString = "#FFFFFF" {
|
||||
didSet {
|
||||
print("KSPlayerView: [PROP SETTER] subtitleTextColor setter called with value: \(subtitleTextColor)")
|
||||
updateSubtitleColors()
|
||||
}
|
||||
}
|
||||
|
||||
@objc var subtitleBackgroundColor: NSString = "rgba(0,0,0,0.7)" {
|
||||
didSet {
|
||||
print("KSPlayerView: [PROP SETTER] subtitleBackgroundColor setter called with value: \(subtitleBackgroundColor)")
|
||||
updateSubtitleColors()
|
||||
}
|
||||
}
|
||||
|
||||
@objc var resizeMode: NSString = "contain" {
|
||||
didSet {
|
||||
print("KSPlayerView: [PROP SETTER] resizeMode setter called with value: \(resizeMode)")
|
||||
|
|
@ -269,6 +284,99 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
print("KSPlayerView: [FONT UPDATE] Applied subtitle font size: \(size)")
|
||||
}
|
||||
|
||||
private func updateSubtitleColors() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
// Parse and apply text color
|
||||
let textColor = self.parseColor(self.subtitleTextColor as String) ?? .white
|
||||
self.playerView.subtitleLabel.textColor = textColor
|
||||
SubtitleModel.textColor = SwiftUI.Color(textColor)
|
||||
|
||||
// Parse and apply background color
|
||||
let bgColor = self.parseColor(self.subtitleBackgroundColor as String) ?? UIColor.black.withAlphaComponent(0.7)
|
||||
self.playerView.subtitleBackView.backgroundColor = bgColor
|
||||
SubtitleModel.textBackgroundColor = SwiftUI.Color(bgColor)
|
||||
|
||||
print("KSPlayerView: [COLOR UPDATE] Applied textColor: \(self.subtitleTextColor), bgColor: \(self.subtitleBackgroundColor)")
|
||||
}
|
||||
}
|
||||
|
||||
private func parseColor(_ colorString: String) -> UIColor? {
|
||||
let trimmed = colorString.trimmingCharacters(in: .whitespaces)
|
||||
|
||||
// Handle hex colors (#RGB, #RRGGBB, #RRGGBBAA)
|
||||
if trimmed.hasPrefix("#") {
|
||||
return parseHexColor(trimmed)
|
||||
}
|
||||
|
||||
// Handle rgba(r, g, b, a) format
|
||||
if trimmed.lowercased().hasPrefix("rgba") {
|
||||
return parseRgbaColor(trimmed)
|
||||
}
|
||||
|
||||
// Handle rgb(r, g, b) format
|
||||
if trimmed.lowercased().hasPrefix("rgb") {
|
||||
return parseRgbColor(trimmed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func parseHexColor(_ hex: String) -> UIColor? {
|
||||
var hexString = hex.trimmingCharacters(in: .whitespaces).uppercased()
|
||||
if hexString.hasPrefix("#") {
|
||||
hexString.remove(at: hexString.startIndex)
|
||||
}
|
||||
|
||||
var rgbValue: UInt64 = 0
|
||||
Scanner(string: hexString).scanHexInt64(&rgbValue)
|
||||
|
||||
switch hexString.count {
|
||||
case 3: // RGB (12-bit)
|
||||
let r = CGFloat((rgbValue & 0xF00) >> 8) / 15.0
|
||||
let g = CGFloat((rgbValue & 0x0F0) >> 4) / 15.0
|
||||
let b = CGFloat(rgbValue & 0x00F) / 15.0
|
||||
return UIColor(red: r, green: g, blue: b, alpha: 1.0)
|
||||
case 6: // RRGGBB (24-bit)
|
||||
let r = CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0
|
||||
let g = CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0
|
||||
let b = CGFloat(rgbValue & 0x0000FF) / 255.0
|
||||
return UIColor(red: r, green: g, blue: b, alpha: 1.0)
|
||||
case 8: // RRGGBBAA (32-bit)
|
||||
let r = CGFloat((rgbValue & 0xFF000000) >> 24) / 255.0
|
||||
let g = CGFloat((rgbValue & 0x00FF0000) >> 16) / 255.0
|
||||
let b = CGFloat((rgbValue & 0x0000FF00) >> 8) / 255.0
|
||||
let a = CGFloat(rgbValue & 0x000000FF) / 255.0
|
||||
return UIColor(red: r, green: g, blue: b, alpha: a)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func parseRgbaColor(_ rgba: String) -> UIColor? {
|
||||
let pattern = "rgba?\\s*\\(\\s*([0-9.]+)\\s*,\\s*([0-9.]+)\\s*,\\s*([0-9.]+)\\s*(?:,\\s*([0-9.]+))?\\s*\\)"
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive),
|
||||
let match = regex.firstMatch(in: rgba, range: NSRange(rgba.startIndex..., in: rgba)),
|
||||
match.numberOfRanges >= 4 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let r = CGFloat((Double((rgba as NSString).substring(with: match.range(at: 1))) ?? 0) / 255.0)
|
||||
let g = CGFloat((Double((rgba as NSString).substring(with: match.range(at: 2))) ?? 0) / 255.0)
|
||||
let b = CGFloat((Double((rgba as NSString).substring(with: match.range(at: 3))) ?? 0) / 255.0)
|
||||
var a: CGFloat = 1.0
|
||||
if match.numberOfRanges >= 5, match.range(at: 4).location != NSNotFound {
|
||||
a = CGFloat(Double((rgba as NSString).substring(with: match.range(at: 4))) ?? 1.0)
|
||||
}
|
||||
|
||||
return UIColor(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
private func parseRgbColor(_ rgb: String) -> UIColor? {
|
||||
return parseRgbaColor(rgb) // Same parsing works for rgb
|
||||
}
|
||||
|
||||
func setSource(_ source: NSDictionary) {
|
||||
currentSource = source
|
||||
|
|
@ -883,10 +991,13 @@ extension KSPlayerView: KSPlayerLayerDelegate {
|
|||
print("KSPlayerView: [READY TO PLAY] ERROR: No subtitle data source available")
|
||||
}
|
||||
|
||||
// Determine player backend type
|
||||
let uriString = currentSource?["uri"] as? String
|
||||
let isMKV = uriString?.lowercased().contains(".mkv") ?? false
|
||||
let playerBackend = isMKV ? "KSMEPlayer" : "KSAVPlayer"
|
||||
// Determine player backend type from actual player instance
|
||||
let playerBackend: String
|
||||
if let _ = layer.player as? KSMEPlayer {
|
||||
playerBackend = "KSMEPlayer"
|
||||
} else {
|
||||
playerBackend = "KSAVPlayer"
|
||||
}
|
||||
|
||||
// Send onLoad event to React Native with track information
|
||||
let p = layer.player
|
||||
|
|
|
|||
635
ios/Podfile.lock
635
ios/Podfile.lock
File diff suppressed because it is too large
Load diff
|
|
@ -204,15 +204,15 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
|||
// Priority: IMDB, TMDB, Tomatoes, Metacritic
|
||||
const priorityOrder = ['imdb', 'tmdb', 'tomatoes', 'metacritic', 'trakt', 'letterboxd', 'audience'];
|
||||
const displayRatings = priorityOrder
|
||||
.filter(source =>
|
||||
source in ratings &&
|
||||
.filter(source =>
|
||||
source in ratings &&
|
||||
ratings[source as keyof typeof ratings] !== undefined &&
|
||||
(enabledProviders[source] ?? true) // Show by default if setting not found
|
||||
)
|
||||
.map(source => [source, ratings[source as keyof typeof ratings]!]);
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
|
|
@ -231,11 +231,11 @@ export const RatingsSection: React.FC<RatingsSectionProps> = ({ imdbId, type })
|
|||
{displayRatings.map(([source, value]) => {
|
||||
const config = ratingConfig[source as keyof typeof ratingConfig];
|
||||
const displayValue = config.transform(parseFloat(value as string));
|
||||
|
||||
|
||||
return (
|
||||
<View key={source} style={[styles.compactRatingItem, { marginRight: itemSpacing }]}>
|
||||
{config.isImage ? (
|
||||
<Image
|
||||
<Image
|
||||
source={config.icon as any}
|
||||
style={[styles.compactRatingIcon, { width: iconSize, height: iconSize, marginRight: iconTextGap }]}
|
||||
resizeMode="contain"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ interface KSPlayerViewProps {
|
|||
usesExternalPlaybackWhileExternalScreenIsActive?: boolean;
|
||||
subtitleBottomOffset?: number;
|
||||
subtitleFontSize?: number;
|
||||
subtitleTextColor?: string;
|
||||
subtitleBackgroundColor?: string;
|
||||
resizeMode?: 'contain' | 'cover' | 'stretch';
|
||||
onLoad?: (data: any) => void;
|
||||
onProgress?: (data: any) => void;
|
||||
|
|
@ -56,6 +58,8 @@ export interface KSPlayerProps {
|
|||
usesExternalPlaybackWhileExternalScreenIsActive?: boolean;
|
||||
subtitleBottomOffset?: number;
|
||||
subtitleFontSize?: number;
|
||||
subtitleTextColor?: string;
|
||||
subtitleBackgroundColor?: string;
|
||||
resizeMode?: 'contain' | 'cover' | 'stretch';
|
||||
onLoad?: (data: any) => void;
|
||||
onProgress?: (data: any) => void;
|
||||
|
|
@ -204,6 +208,8 @@ const KSPlayer = forwardRef<KSPlayerRef, KSPlayerProps>((props, ref) => {
|
|||
usesExternalPlaybackWhileExternalScreenIsActive={props.usesExternalPlaybackWhileExternalScreenIsActive}
|
||||
subtitleBottomOffset={props.subtitleBottomOffset}
|
||||
subtitleFontSize={props.subtitleFontSize}
|
||||
subtitleTextColor={props.subtitleTextColor}
|
||||
subtitleBackgroundColor={props.subtitleBackgroundColor}
|
||||
resizeMode={props.resizeMode}
|
||||
onLoad={(e: any) => props.onLoad?.(e?.nativeEvent ?? e)}
|
||||
onProgress={(e: any) => props.onProgress?.(e?.nativeEvent ?? e)}
|
||||
|
|
|
|||
|
|
@ -546,6 +546,10 @@ const KSPlayerCore: React.FC = () => {
|
|||
screenWidth={screenDimensions.width}
|
||||
screenHeight={screenDimensions.height}
|
||||
customVideoStyles={{ width: '100%', height: '100%' }}
|
||||
subtitleTextColor={customSubs.subtitleTextColor}
|
||||
subtitleBackgroundColor={customSubs.subtitleBackground ? `rgba(0,0,0,${customSubs.subtitleBgOpacity})` : 'transparent'}
|
||||
subtitleFontSize={customSubs.subtitleSize}
|
||||
subtitleBottomOffset={customSubs.subtitleBottomOffset}
|
||||
/>
|
||||
|
||||
{/* Custom Subtitles Overlay */}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ interface KSPlayerSurfaceProps {
|
|||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
customVideoStyles: any;
|
||||
|
||||
// Subtitle styling
|
||||
subtitleTextColor?: string;
|
||||
subtitleBackgroundColor?: string;
|
||||
subtitleFontSize?: number;
|
||||
subtitleBottomOffset?: number;
|
||||
}
|
||||
|
||||
export const KSPlayerSurface: React.FC<KSPlayerSurfaceProps> = ({
|
||||
|
|
@ -64,7 +70,11 @@ export const KSPlayerSurface: React.FC<KSPlayerSurfaceProps> = ({
|
|||
onPlaybackResume,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
customVideoStyles
|
||||
customVideoStyles,
|
||||
subtitleTextColor,
|
||||
subtitleBackgroundColor,
|
||||
subtitleFontSize,
|
||||
subtitleBottomOffset
|
||||
}) => {
|
||||
const pinchRef = useRef<PinchGestureHandler>(null);
|
||||
|
||||
|
|
@ -132,6 +142,10 @@ export const KSPlayerSurface: React.FC<KSPlayerSurfaceProps> = ({
|
|||
resizeMode={resizeMode}
|
||||
audioTrack={audioTrack}
|
||||
textTrack={textTrack}
|
||||
subtitleTextColor={subtitleTextColor}
|
||||
subtitleBackgroundColor={subtitleBackgroundColor}
|
||||
subtitleFontSize={subtitleFontSize}
|
||||
subtitleBottomOffset={subtitleBottomOffset}
|
||||
onLoad={handleLoad}
|
||||
onProgress={onProgress}
|
||||
onBuffering={handleBuffering}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
const [activeTab, setActiveTab] = React.useState<'built-in' | 'addon' | 'appearance'>('built-in');
|
||||
|
||||
const isCompact = width < 360 || height < 640;
|
||||
// Internal subtitle is active when a built-in track is selected AND not using custom/addon subtitles
|
||||
const isUsingInternalSubtitle = selectedTextTrack >= 0 && !useCustomSubtitles;
|
||||
const sectionPad = isCompact ? 12 : 16;
|
||||
const chipPadH = isCompact ? 8 : 12;
|
||||
const chipPadV = isCompact ? 6 : 8;
|
||||
|
|
@ -363,64 +365,72 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
|
|||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Text Shadow</Text>
|
||||
<TouchableOpacity onPress={() => setSubtitleTextShadow(!subtitleTextShadow)} style={{ paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10, backgroundColor: subtitleTextShadow ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center' }}>
|
||||
<Text style={{ color: '#fff', fontWeight: '700' }}>{subtitleTextShadow ? 'On' : 'Off'}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Color</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
{['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
|
||||
<TouchableOpacity key={c} onPress={() => setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleOutlineColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Width</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(Math.max(0, subtitleOutlineWidth - 1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 42, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleOutlineWidth}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(subtitleOutlineWidth + 1)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
{!isUsingInternalSubtitle && (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Text Shadow</Text>
|
||||
<TouchableOpacity onPress={() => setSubtitleTextShadow(!subtitleTextShadow)} style={{ paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10, backgroundColor: subtitleTextShadow ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.08)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center' }}>
|
||||
<Text style={{ color: '#fff', fontWeight: '700' }}>{subtitleTextShadow ? 'On' : 'Off'}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: isCompact ? 'column' : 'row', justifyContent: 'space-between', gap: 12 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Letter Spacing</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(Math.max(0, +(subtitleLetterSpacing - 0.5).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 48, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleLetterSpacing.toFixed(1)}</Text>
|
||||
)}
|
||||
{!isUsingInternalSubtitle && (
|
||||
<>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Color</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8 }}>
|
||||
{['#000000', '#FFFFFF', '#00E5FF', '#FF5C5C'].map(c => (
|
||||
<TouchableOpacity key={c} onPress={() => setSubtitleOutlineColor(c)} style={{ width: 22, height: 22, borderRadius: 11, backgroundColor: c, borderWidth: 2, borderColor: subtitleOutlineColor === c ? '#fff' : 'rgba(255,255,255,0.3)' }} />
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white' }}>Outline Width</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(Math.max(0, subtitleOutlineWidth - 1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 42, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleOutlineWidth}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleOutlineWidth(subtitleOutlineWidth + 1)} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
{!isUsingInternalSubtitle && (
|
||||
<View style={{ flexDirection: isCompact ? 'column' : 'row', justifyContent: 'space-between', gap: 12 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Letter Spacing</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(Math.max(0, +(subtitleLetterSpacing - 0.5).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 48, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleLetterSpacing.toFixed(1)}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(+(subtitleLetterSpacing + 0.5).toFixed(1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Line Height</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(Math.max(1, +(subtitleLineHeightMultiplier - 0.1).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 48, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleLineHeightMultiplier.toFixed(1)}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(+(subtitleLineHeightMultiplier + 0.1).toFixed(1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleLetterSpacing(+(subtitleLetterSpacing + 0.5).toFixed(1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Line Height</Text>
|
||||
<View style={{ flexDirection: 'row', gap: 8, alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(Math.max(1, +(subtitleLineHeightMultiplier - 0.1).toFixed(1)))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="remove" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
<View style={{ minWidth: 48, paddingHorizontal: 6, paddingVertical: 4, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.12)' }}>
|
||||
<Text style={{ color: 'white', textAlign: 'center', fontWeight: '700' }}>{subtitleLineHeightMultiplier.toFixed(1)}</Text>
|
||||
</View>
|
||||
<TouchableOpacity onPress={() => setSubtitleLineHeightMultiplier(+(subtitleLineHeightMultiplier + 0.1).toFixed(1))} style={{ width: controlBtn.size, height: controlBtn.size, borderRadius: controlBtn.radius, backgroundColor: 'rgba(255,255,255,0.18)', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<MaterialIcons name="add" color="#fff" size={18} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ marginTop: 4 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Text style={{ color: 'white', fontWeight: '600' }}>Timing Offset (s)</Text>
|
||||
|
|
|
|||
Loading…
Reference in a new issue