@zezosoft/zezo-ott-react-native-video-player
Version:
React Native OTT Video Player for Android & iOS. Supports playlists, seasons, auto-next playback, subtitles, theming, analytics, fullscreen mode, and custom controls. 🚀 Powered by ZezoSoft.
257 lines (249 loc) • 7 kB
JavaScript
"use strict";
import React, { useMemo } from 'react';
import { FlatList, Platform, TouchableOpacity, View, Text, StyleSheet } from 'react-native';
import { scale, verticalScale } from 'react-native-size-matters';
import { RFValue } from 'react-native-responsive-fontsize';
import { useVideoPlayerStore } from "../store/index.js";
import { useVideoPlayerConfig } from "../context/index.js";
// --- Constants ---
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const ALLOWED_SUBTITLE_TYPES = ['application/x-subrip', 'text/vtt', 'application/ttml+xml'];
// --- Helpers ---
const mapSubtitleType = mimeType => {
switch (mimeType) {
case 'application/x-subrip':
return 'srt';
case 'application/ttml+xml':
return 'ttml';
case 'text/vtt':
return 'vtt';
default:
return 'srt';
}
};
// --- UI Components ---
const Separator = () => /*#__PURE__*/_jsx(View, {
style: styles.separator
});
const AudioItem = ({
item,
isSelected,
onPress,
colors
}) => /*#__PURE__*/_jsx(TouchableOpacity, {
onPress: onPress,
children: /*#__PURE__*/_jsx(Text, {
style: [styles.listItem, {
color: colors.text
}, isSelected && styles.selected],
children: item.title
})
});
const SubtitleItem = ({
item,
isSelected,
onPress,
colors
}) => /*#__PURE__*/_jsx(TouchableOpacity, {
onPress: onPress,
children: /*#__PURE__*/_jsx(Text, {
style: [styles.listItem, {
color: colors.text
}, isSelected && styles.selected],
children: item.title || 'Unknown'
})
});
// --- Main Component ---
const AudioAndSubtitles = () => {
const {
setSettingsModal,
onLoad,
setSelectedAudioTrack,
setSelectedSubtitleTrack,
selectedAudioTrack,
selectedSubtitleTrack,
setActiveSubtitle,
activeTrack,
activeSubtitle,
setIsPaused
} = useVideoPlayerStore();
const {
colors
} = useVideoPlayerConfig();
// Filter audio tracks
const audioTracks = useMemo(() => {
return onLoad?.audioTracks?.filter(item => item.title !== null && item.title !== '') || [];
}, [onLoad?.audioTracks]);
// Merge internal & external subtitles
const subtitleTracks = useMemo(() => {
const internal = onLoad?.textTracks?.filter(track => {
const type = String(track.type);
const isCEA608 = Platform.OS === 'android' && type === 'application/cea-608';
return !isCEA608 && ALLOWED_SUBTITLE_TYPES.includes(type) && track.title?.trim();
}).map((track, idx) => ({
index: idx,
title: track.title,
language: track.language,
type: track.type ? mapSubtitleType(String(track.type)) : 'srt',
value: idx,
isExternal: false
})) || [];
const external = activeTrack?.subtitles?.map((item, idx) => ({
index: internal.length + idx,
type: mapSubtitleType(item.type),
value: internal.length + idx,
language: item.language,
title: item.title || item.language || `Subtitle ${idx + 1}`,
uri: item.url,
isExternal: true
})) || [];
return [...internal, ...external];
}, [activeTrack?.subtitles, onLoad?.textTracks]);
// Handlers
const handleSelectAudio = item => {
setSelectedAudioTrack({
type: 'index',
value: item.index,
isExternal: false
});
setSettingsModal({
isVisible: false,
action: 'none'
});
};
const handleSelectSubtitle = item => {
if (item.isExternal) {
setActiveSubtitle({
type: item.type,
url: item.uri,
language: item.language,
title: item.title
});
setSelectedSubtitleTrack({
type: 'index',
value: 'Off',
isExternal: false
});
} else {
setSelectedSubtitleTrack({
type: 'index',
value: Number(item.value),
isExternal: false
});
setActiveSubtitle(null);
}
setIsPaused(false);
setSettingsModal({
isVisible: false,
action: 'none'
});
};
const handleDisableSubtitles = () => {
setSelectedSubtitleTrack({
type: 'index',
value: 'Off',
isExternal: false
});
setActiveSubtitle(null);
setIsPaused(false);
setSettingsModal({
isVisible: false,
action: 'none'
});
};
return /*#__PURE__*/_jsxs(View, {
style: styles.container,
children: [/*#__PURE__*/_jsx(View, {
style: styles.section,
children: audioTracks.length > 0 && /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.sectionTitle, {
color: colors.text
}],
children: "Audio"
}), /*#__PURE__*/_jsx(FlatList, {
data: audioTracks,
keyExtractor: ({
index
}) => `audio-${index}`,
ItemSeparatorComponent: Separator,
renderItem: ({
item
}) => /*#__PURE__*/_jsx(AudioItem, {
item: item,
isSelected: selectedAudioTrack?.value === item.index,
onPress: () => handleSelectAudio(item),
colors: colors
})
})]
})
}), /*#__PURE__*/_jsxs(View, {
style: styles.section,
children: [/*#__PURE__*/_jsx(Text, {
style: [styles.sectionTitle, {
color: colors.text
}],
children: "Subtitles"
}), /*#__PURE__*/_jsx(TouchableOpacity, {
onPress: handleDisableSubtitles,
children: /*#__PURE__*/_jsx(Text, {
style: [styles.listItem, {
color: colors.text
}, !activeSubtitle && selectedSubtitleTrack?.value === 'Off' || selectedSubtitleTrack === null ? styles.selected : null],
children: "Off"
})
}), /*#__PURE__*/_jsx(Separator, {}), /*#__PURE__*/_jsx(FlatList, {
data: subtitleTracks,
keyExtractor: ({
index
}) => `sub-${index}`,
ItemSeparatorComponent: Separator,
renderItem: ({
item
}) => /*#__PURE__*/_jsx(SubtitleItem, {
item: item,
isSelected: selectedSubtitleTrack?.value === item.index || activeSubtitle?.language === item.language,
onPress: () => handleSelectSubtitle(item),
colors: {
text: colors.text
}
})
})]
})]
});
};
export default AudioAndSubtitles;
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
padding: scale(8),
gap: scale(12)
},
section: {
flex: 1,
borderRadius: scale(12),
padding: scale(8)
},
sectionTitle: {
fontSize: RFValue(20),
fontWeight: '600',
marginBottom: verticalScale(8),
letterSpacing: 0.6
},
listItem: {
fontSize: RFValue(17),
paddingVertical: verticalScale(4),
paddingHorizontal: scale(12),
borderRadius: scale(8),
opacity: 0.7
},
selected: {
opacity: 1,
fontWeight: '700'
},
separator: {
height: verticalScale(8)
}
});
//# sourceMappingURL=AudioAndSubtitles.js.map