From 4e2e914a3f35f273f9b24dd1468a474656536dc7 Mon Sep 17 00:00:00 2001 From: Kestas Venslauskas Date: Thu, 16 May 2024 16:01:40 +0300 Subject: [PATCH 1/4] feat: carplay standalone mode --- .vscode/settings.json | 1 + App.tsx | 17 ++-- PlaybackService.ts | 82 +++++++++++++++++++ app/api/Endpoints.ts | 2 + app/api/Types.ts | 16 +++- app/api/index.ts | 5 ++ app/car/live/useCarLiveChannels.ts | 28 ++++--- .../nowPlaying/createNowPlayingTemplate.ts | 12 +++ app/car/useCarPlay.ts | 6 -- app/car/useCarPlayController.ts | 5 +- app/car/useTrackPlayerSetup.ts | 52 ++++++++++++ .../videoComponent/TheoMediaPlayer.tsx | 6 +- .../videoComponent/VideoComponent.tsx | 8 +- .../context/DefaultPlayerContext.ts | 26 ++++++ .../videoComponent/context/PlayerContext.ts | 12 +-- .../videoComponent/fetchStreamData.ts | 8 +- .../videoComponent/useStreamData.ts | 2 + app/constants/index.ts | 3 + .../context/ChannelContextProvider.tsx | 14 +++- index.js | 5 ++ ios/Podfile.lock | 10 +++ ios/lrtApp.xcodeproj/project.pbxproj | 8 +- ios/lrtApp/CarScene.swift | 19 +++-- package.json | 3 +- 24 files changed, 298 insertions(+), 52 deletions(-) create mode 100644 PlaybackService.ts delete mode 100644 app/car/useCarPlay.ts create mode 100644 app/car/useTrackPlayerSetup.ts create mode 100644 app/components/videoComponent/context/DefaultPlayerContext.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b9073bc..829397a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "Epika", "GEMIUS", "KLASIKA", + "LITHUANICA", "notifee", "RADIJAS", "Theo", diff --git a/App.tsx b/App.tsx index 78dc4a2..9f52773 100644 --- a/App.tsx +++ b/App.tsx @@ -10,12 +10,13 @@ import {initialWindowMetrics, SafeAreaProvider} from 'react-native-safe-area-con import {persistor, store} from './app/redux/store'; import useAppTrackingPermission from './app/util/useAppTrackingPermission'; import {GestureHandlerRootView} from 'react-native-gesture-handler'; -import {StyleSheet} from 'react-native'; +import {Platform, StyleSheet} from 'react-native'; import PlayerProvider from './app/components/videoComponent/context/PlayerProvider'; import useNotificationsPermission from './app/util/useNotificationsPermission'; import useGoogleAnalyticsSetup from './app/util/useGoogleAnalyticsSetup'; -import CarPlayProvider from './app/car/CarPlayProvider'; import ThemeProvider from './app/theme/ThemeProvider'; +import useCarPlayController from './app/car/useCarPlayController'; +import useTrackPlayerSetup from './app/car/useTrackPlayerSetup'; const ReduxProvider: React.FC> = ({children}) => { return ( @@ -32,6 +33,14 @@ const App: React.FC = () => { useAppTrackingPermission(); useGoogleAnalyticsSetup(); useGemiusSetup(); + useTrackPlayerSetup(); + + const {isConnected: _} = + Platform.OS === 'ios' + ? useCarPlayController() + : { + isConnected: false, + }; return ( @@ -39,9 +48,7 @@ const App: React.FC = () => { - - - + diff --git a/PlaybackService.ts b/PlaybackService.ts new file mode 100644 index 0000000..28b8b57 --- /dev/null +++ b/PlaybackService.ts @@ -0,0 +1,82 @@ +import TrackPlayer, {Event} from 'react-native-track-player'; + +const PlaybackService = async () => { + TrackPlayer.addEventListener(Event.RemotePause, () => { + console.log('Event.RemotePause'); + TrackPlayer.pause(); + }); + TrackPlayer.addEventListener(Event.RemotePlay, () => { + console.log('Event.RemotePlay'); + TrackPlayer.play(); + }); + TrackPlayer.addEventListener(Event.RemoteStop, () => { + console.log('Event.RemoteStop'); + TrackPlayer.stop(); + }); + TrackPlayer.addEventListener(Event.RemoteNext, () => { + console.log('Event.RemoteNext'); + TrackPlayer.skipToNext(); + }); + TrackPlayer.addEventListener(Event.RemotePrevious, () => { + console.log('Event.RemotePrevious'); + TrackPlayer.skipToPrevious(); + }); + TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => { + console.log('Event.RemoteJumpForward', event); + TrackPlayer.seekBy(event.interval); + }); + TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => { + console.log('Event.RemoteJumpBackward', event); + TrackPlayer.seekBy(-event.interval); + }); + TrackPlayer.addEventListener(Event.RemoteSeek, (event) => { + console.log('Event.RemoteSeek', event); + TrackPlayer.seekTo(event.position); + }); + // TrackPlayer.addEventListener(Event.RemoteDuck, async (event) => { + // console.log('Event.RemoteDuck', event); + // }); + // TrackPlayer.addEventListener(Event.PlaybackQueueEnded, (event) => { + // console.log('Event.PlaybackQueueEnded', event); + // }); + // TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, (event) => { + // console.log('Event.PlaybackActiveTrackChanged', event); + // }); + // TrackPlayer.addEventListener(Event.PlaybackProgressUpdated, (event) => { + // console.log('Event.PlaybackProgressUpdated', event); + // }); + // TrackPlayer.addEventListener(Event.PlaybackPlayWhenReadyChanged, (event) => { + // console.log('Event.PlaybackPlayWhenReadyChanged', event); + // }); + TrackPlayer.addEventListener(Event.PlaybackState, (event) => { + console.log('Event.PlaybackState', event); + }); + // TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, (event) => { + // console.log('[Deprecated] Event.PlaybackMetadataReceived', event); + // }); + // TrackPlayer.addEventListener(Event.MetadataChapterReceived, (event) => { + // console.log('Event.MetadataChapterReceived', event); + // }); + // TrackPlayer.addEventListener(Event.MetadataTimedReceived, (event) => { + // console.log('Event.MetadataTimedReceived', event); + // }); + // TrackPlayer.addEventListener(Event.MetadataCommonReceived, (event) => { + // console.log('Event.MetadataCommonReceived', event); + // }); + // TrackPlayer.addEventListener(Event.PlaybackProgressUpdated, (event) => { + // console.log('Event.PlaybackProgressUpdated', event); + // }); + // TrackPlayer.addEventListener(Event.MetadataCommonReceived, async ({metadata}) => { + // const activeTrack = await TrackPlayer.getActiveTrack(); + // console.log('Event.MetadataCommonReceived', metadata, activeTrack); + // TrackPlayer.updateNowPlayingMetadata({ + // artist: [metadata.title, metadata.artist].filter(Boolean).join(' - '), + // title: activeTrack?.title ?? metadata.title, + // artwork: activeTrack?.artwork ?? metadata.artworkUri, + // duration: activeTrack?.duration, + // isLiveStream: activeTrack?.isLiveStream, + // }); + // }); +}; + +export default PlaybackService; diff --git a/app/api/Endpoints.ts b/app/api/Endpoints.ts index 37a4ac5..3687c7a 100644 --- a/app/api/Endpoints.ts +++ b/app/api/Endpoints.ts @@ -175,3 +175,5 @@ export const carPlaylistPodcastsGet = (count: number) => export const carPlaylistCategoryGet = (id: number | string) => `https://www.lrt.lt/api/json/category?id=${id}`; + +export const carPlaylistLiveGet = () => 'https://www.lrt.lt/static/tvprog/tvprog.json'; diff --git a/app/api/Types.ts b/app/api/Types.ts index 07641fe..f18156a 100644 --- a/app/api/Types.ts +++ b/app/api/Types.ts @@ -66,6 +66,20 @@ export type MenuResponse = { main_menu: (MenuItem | MenuItemCategory | MenuItemPage | MenuItemChannels | MenuItemProjects)[]; }; +export type TVProgramResponse = { + tvprog: { + has_tvprog?: 0 | 1; + items: TVProgramChannel[]; + live_items?: TVProgramChannel[]; + }; +}; + +export type TVProgramChannel = Omit & { + description?: string; + stream_url: string; + app_logo: string; +}; + /** * Channel type in home page */ @@ -79,7 +93,7 @@ export type TVChannel = { certification: string; time_start: string; time_end: string; - stream_embed: string; + stream_embed?: string; get_streams_url: string; is_radio?: 0 | 1; block_all?: 0 | 1; diff --git a/app/api/index.ts b/app/api/index.ts index 88d1078..457f065 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -3,6 +3,7 @@ import { articlesGetByTag, audiotekaGet, carPlaylistCategoryGet, + carPlaylistLiveGet, carPlaylistNewestGet, carPlaylistPodcastsGet, carPlaylistPopularGet, @@ -46,6 +47,8 @@ import { SearchFilter, SearchResponse, SlugArticlesResponse, + TVProgramChannel, + TVProgramResponse, VideoDataDefault, VideoDataLiveStream, } from './Types'; @@ -113,3 +116,5 @@ export const fetchCarPodcasts = (count: number) => export const fetchCarCategoryPlaylist = (id: string | number) => get(carPlaylistCategoryGet(id)); + +export const fetchCarLivePlaylist = () => get(carPlaylistLiveGet()); diff --git a/app/car/live/useCarLiveChannels.ts b/app/car/live/useCarLiveChannels.ts index 096130c..d1b64fd 100644 --- a/app/car/live/useCarLiveChannels.ts +++ b/app/car/live/useCarLiveChannels.ts @@ -1,16 +1,14 @@ -import {useSelector} from 'react-redux'; -import {selectHomeChannels} from '../../redux/selectors'; -import {checkEqual} from '../../util/LodashEqualityCheck'; import {useEffect, useState} from 'react'; import {PlayListItem} from '../CarPlayContext'; import {fetchStreamData} from '../../components/videoComponent/fetchStreamData'; import useCancellablePromise from '../../hooks/useCancellablePromise'; import {VIDEO_DEFAULT_BACKGROUND_IMAGE} from '../../constants'; +import {fetchCarLivePlaylist} from '../../api'; +import {BASE_IMG_URL} from '../../util/ImageUtil'; const useCarLiveChannels = (isConnected: boolean) => { const [channels, setChannels] = useState([]); const [lastLoadTime, setLastLoadTime] = useState(0); - const channelsData = useSelector(selectHomeChannels, checkEqual); const cancellablePromise = useCancellablePromise(); @@ -20,14 +18,18 @@ const useCarLiveChannels = (isConnected: boolean) => { } cancellablePromise( - Promise.all( - channelsData?.channels?.map((channel) => - fetchStreamData({ - url: channel.get_streams_url, - title: channel.channel_title, - poster: channel.cover_url, - }), - ) || [], + fetchCarLivePlaylist().then((response) => + Promise.all( + response.tvprog?.items?.map((channel) => + fetchStreamData({ + url: channel.stream_url, + title: channel.channel_title, + poster: channel.cover_url.startsWith('http') + ? channel.cover_url + : BASE_IMG_URL + channel.cover_url, + }), + ) || [], + ), ), ).then((data) => { if (data?.length) { @@ -41,7 +43,7 @@ const useCarLiveChannels = (isConnected: boolean) => { setChannels(channels); } }); - }, [channelsData, isConnected, lastLoadTime]); + }, [isConnected, lastLoadTime]); return { channels, diff --git a/app/car/nowPlaying/createNowPlayingTemplate.ts b/app/car/nowPlaying/createNowPlayingTemplate.ts index 985ca47..aa9ee66 100644 --- a/app/car/nowPlaying/createNowPlayingTemplate.ts +++ b/app/car/nowPlaying/createNowPlayingTemplate.ts @@ -33,4 +33,16 @@ export const createNowPlayingTemplate = ({onNextPress, onPreviousPress}: Props) export const carPlayNowPlayingTemplate = new NowPlayingTemplate({ id: 'lrt-now-playing-template', albumArtistButtonEnabled: true, + onBarButtonPressed: (button) => { + console.log('onBarButtonPressed', button); + }, + onButtonPressed: (button) => { + console.log('onButtonPressed', button); + }, + onAlbumArtistButtonPressed: () => { + console.log('onAlbumArtistButtonPressed'); + }, + onUpNextButtonPressed: () => { + console.log('onUpNextButtonPressed'); + }, }); diff --git a/app/car/useCarPlay.ts b/app/car/useCarPlay.ts deleted file mode 100644 index 5a5322f..0000000 --- a/app/car/useCarPlay.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {useContext} from 'react'; -import {CarPlayContext, CarPlayContextType} from './CarPlayContext'; - -export function useCarPlay(): CarPlayContextType { - return useContext(CarPlayContext); -} diff --git a/app/car/useCarPlayController.ts b/app/car/useCarPlayController.ts index 87b173b..522310c 100644 --- a/app/car/useCarPlayController.ts +++ b/app/car/useCarPlayController.ts @@ -4,11 +4,12 @@ import {CarPlayContextType} from './CarPlayContext'; import useCarPlayRootTemplate from './root/useCarPlayRootTemplate'; import Gemius from 'react-native-gemius-plugin'; import analytics from '@react-native-firebase/analytics'; +import TrackPlayer from 'react-native-track-player'; type ReturnType = CarPlayContextType; const useCarPlayController = (): ReturnType => { - const [isConnected, setIsConnected] = useState(false); + const [isConnected, setIsConnected] = useState(CarPlay.connected); const rootTemplate = useCarPlayRootTemplate(isConnected); @@ -16,6 +17,8 @@ const useCarPlayController = (): ReturnType => { if (isConnected) { CarPlay.setRootTemplate(rootTemplate); CarPlay.enableNowPlaying(true); + } else { + TrackPlayer.reset(); } }, [isConnected]); diff --git a/app/car/useTrackPlayerSetup.ts b/app/car/useTrackPlayerSetup.ts new file mode 100644 index 0000000..4bc249a --- /dev/null +++ b/app/car/useTrackPlayerSetup.ts @@ -0,0 +1,52 @@ +import {useCallback, useEffect} from 'react'; +import TrackPlayer, {Capability, IOSCategory, IOSCategoryMode, RepeatMode} from 'react-native-track-player'; + +const useTrackPlayerSetup = () => { + const setup = useCallback(async () => { + await TrackPlayer.setupPlayer({ + iosCategory: IOSCategory.Playback, + iosCategoryMode: IOSCategoryMode.SpokenAudio, + autoHandleInterruptions: true, + autoUpdateMetadata: true, + //androidAudioContentType: 'music', + }); + await TrackPlayer.updateOptions({ + capabilities: [ + Capability.Play, + Capability.Pause, + Capability.SkipToNext, + Capability.SkipToPrevious, + Capability.JumpBackward, + Capability.JumpForward, + Capability.SeekTo, + Capability.Stop, + ], + notificationCapabilities: [ + Capability.Play, + Capability.Pause, + Capability.SkipToNext, + Capability.SkipToPrevious, + Capability.JumpBackward, + Capability.JumpForward, + Capability.SeekTo, + ], + compactCapabilities: [ + Capability.Play, + Capability.Pause, + Capability.SkipToNext, + Capability.SkipToPrevious, + Capability.JumpBackward, + Capability.JumpForward, + Capability.SeekTo, + ], + }); + await TrackPlayer.setRepeatMode(RepeatMode.Queue); + console.log('TrackPlayer ready!'); + }, []); + + useEffect(() => { + setup(); + }, []); +}; + +export default useTrackPlayerSetup; diff --git a/app/components/videoComponent/TheoMediaPlayer.tsx b/app/components/videoComponent/TheoMediaPlayer.tsx index 2bb5a3b..8d132e9 100644 --- a/app/components/videoComponent/TheoMediaPlayer.tsx +++ b/app/components/videoComponent/TheoMediaPlayer.tsx @@ -25,6 +25,7 @@ import {MediaPlayerState} from 'react-native-google-cast'; import {useTheme} from '../../Theme'; import usePlayerLanguage from './usePlayerLanguage'; import {EventRegister} from 'react-native-event-listeners'; +import FastImage from 'react-native-fast-image'; export type PlayerAction = 'togglePlay' | 'setFullScreen'; @@ -46,8 +47,7 @@ interface Props { const license = Platform.select({ android: 'sZP7IYe6T6Pe3uPKTuX136ztIuhtFSacIQC-ClhoTOziTuR_3D4e3uetIS06FOPlUY3zWokgbgjNIOf9fKCi0L0cTSRtFDCkIlR-3Q363ZzrTuarFS0L0SA1ISerIl3K0mfVfK4_bQgZCYxNWoryIQXzImf90SbiTSf_3Lfi0u5i0Oi6Io4pIYP1UQgqWgjeCYxgflEc3lbk0SCk3SbcTS5tFOPeWok1dDrLYtA1Ioh6TgV6v6fVfKcqCoXVdQjLUOfVfGxEIDjiWQXrIYfpCoj-fgzVfKxqWDXNWG3ybojkbK3gflNWf6E6FOPVWo31WQ1qbta6FOPzdQ4qbQc1sD4ZFK3qWmPUFOPLIQ-LflNWfK1zWDikf6i6CDrebKjNIOfVfKXpIwPqdDxzU6fVfKINbK4zU6fVfKgqbZfVfGxNsK4pf6i6UwIqbZfVfGUgCKjLfgzVfG3gWKxydDkibK4LbogqW6f9UwPkImi6IK41Uw4ZIY06Tg-Uya', - ios: - 'sZP7IYe6T6Pe3uPKTuX136ztIuhtFSacIQC-ClhoTOziTuR_3D4e3uetIS06FOPlUY3zWokgbgjNIOf9fKCi0L0cTSRtFDCkIlR-3Q363ZzrTuarFS0L0SA1ISerIl3K0mfVfK4_bQgZCYxNWoryIQXzImf90SbiTSf_3Lfi0u5i0Oi6Io4pIYP1UQgqWgjeCYxgflEc3lbk0SCk3SbcTS5tFOPeWok1dDrLYtA1Ioh6TgV6v6fVfKcqCoXVdQjLUOfVfGxEIDjiWQXrIYfpCoj-fgzVfKxqWDXNWG3ybojkbK3gflNWf6E6FOPVWo31WQ1qbta6FOPzdQ4qbQc1sD4ZFK3qWmPUFOPLIQ-LflNWfK1zWDikf6i6CDrebKjNIOfVfKXpIwPqdDxzU6fVfKINbK4zU6fVfKgqbZfVfGxNsK4pf6i6UwIqbZfVfGUgCKjLfgzVfG3gWKxydDkibK4LbogqW6f9UwPkImi6IK41Uw4ZIY06Tg-Uya', + ios: 'sZP7IYe6T6Pe3uPKTuX136ztIuhtFSacIQC-ClhoTOziTuR_3D4e3uetIS06FOPlUY3zWokgbgjNIOf9fKCi0L0cTSRtFDCkIlR-3Q363ZzrTuarFS0L0SA1ISerIl3K0mfVfK4_bQgZCYxNWoryIQXzImf90SbiTSf_3Lfi0u5i0Oi6Io4pIYP1UQgqWgjeCYxgflEc3lbk0SCk3SbcTS5tFOPeWok1dDrLYtA1Ioh6TgV6v6fVfKcqCoXVdQjLUOfVfGxEIDjiWQXrIYfpCoj-fgzVfKxqWDXNWG3ybojkbK3gflNWf6E6FOPVWo31WQ1qbta6FOPzdQ4qbQc1sD4ZFK3qWmPUFOPLIQ-LflNWfK1zWDikf6i6CDrebKjNIOfVfKXpIwPqdDxzU6fVfKINbK4zU6fVfKgqbZfVfGxNsK4pf6i6UwIqbZfVfGUgCKjLfgzVfG3gWKxydDkibK4LbogqW6f9UwPkImi6IK41Uw4ZIY06Tg-Uya', }); const config: PlayerConfiguration = { @@ -374,7 +374,7 @@ const TheoMediaPlayer: React.FC> = ({ <> {mediaType == MediaType.AUDIO ? ( - + ) : null} {isLoading && ( diff --git a/app/components/videoComponent/VideoComponent.tsx b/app/components/videoComponent/VideoComponent.tsx index c7e5f7d..06b31de 100644 --- a/app/components/videoComponent/VideoComponent.tsx +++ b/app/components/videoComponent/VideoComponent.tsx @@ -105,7 +105,13 @@ const VideoComponent: React.FC> = (props) => { isLiveStream={data.isLiveStream} startTime={data.isLiveStream ? undefined : props.startTime || data.offset} poster={props.backgroundImage ?? data.poster} - mediaType={data.streamUri.includes('audio') ? MediaType.AUDIO : MediaType.VIDEO} + mediaType={ + data.mediaType != undefined + ? data.mediaType + : data.streamUri.includes('audio') + ? MediaType.AUDIO + : MediaType.VIDEO + } onError={onPlayerError} /> diff --git a/app/components/videoComponent/context/DefaultPlayerContext.ts b/app/components/videoComponent/context/DefaultPlayerContext.ts new file mode 100644 index 0000000..dd1d357 --- /dev/null +++ b/app/components/videoComponent/context/DefaultPlayerContext.ts @@ -0,0 +1,26 @@ +import {PlayerContextType} from './PlayerContext'; +import TrackPlayer, {PitchAlgorithm} from 'react-native-track-player'; + +const defaultPlayerContext: PlayerContextType = { + setPlaylist: async (data, current) => { + await TrackPlayer.setQueue( + data.map((item) => ({ + url: item.uri, + artwork: item.poster, + title: item.title, + pitchAlgorithm: PitchAlgorithm.Voice, + isLiveStream: item.isLiveStream, + })), + ); + + if (current !== undefined) { + await TrackPlayer.skip(current); + } + await TrackPlayer.play(); + }, + playNext: () => console.log('playNext'), + playPrevious: () => console.log('playPrevious'), + close: () => TrackPlayer.reset(), +}; + +export default defaultPlayerContext; diff --git a/app/components/videoComponent/context/PlayerContext.ts b/app/components/videoComponent/context/PlayerContext.ts index 43aefe5..0640447 100644 --- a/app/components/videoComponent/context/PlayerContext.ts +++ b/app/components/videoComponent/context/PlayerContext.ts @@ -1,4 +1,5 @@ import React from 'react'; +import defaultPlayerContext from './DefaultPlayerContext'; // eslint-disable-next-line no-shadow export enum MediaType { @@ -22,13 +23,4 @@ export type PlayerContextType = { close: () => void; }; -const noOp = (): any => { - console.warn('VideoContext: NO OP CALLED!'); -}; - -export const PlayerContext = React.createContext({ - setPlaylist: noOp, - playNext: noOp, - playPrevious: noOp, - close: noOp, -}); +export const PlayerContext = React.createContext(defaultPlayerContext); diff --git a/app/components/videoComponent/fetchStreamData.ts b/app/components/videoComponent/fetchStreamData.ts index c89ae72..976cb3c 100644 --- a/app/components/videoComponent/fetchStreamData.ts +++ b/app/components/videoComponent/fetchStreamData.ts @@ -7,10 +7,12 @@ export const fetchStreamData = ({ url, title, poster, + prioritizeAudio, }: { url: string; title?: string; poster?: string; + prioritizeAudio?: boolean; }): Promise => fetchVideoData(url) .then((response) => { @@ -19,7 +21,11 @@ export const fetchStreamData = ({ return { channelTitle: title, isLiveStream: true, - streamUri: data.content.trim(), + streamUri: prioritizeAudio + ? data.audio + ? data.audio.trim() + : data.content.trim() + : data.content.trim(), title: title ?? 'untitled-live-stream', poster: poster, mediaId: data.content, diff --git a/app/components/videoComponent/useStreamData.ts b/app/components/videoComponent/useStreamData.ts index 98b2064..f482419 100644 --- a/app/components/videoComponent/useStreamData.ts +++ b/app/components/videoComponent/useStreamData.ts @@ -1,6 +1,7 @@ import {useCallback, useEffect, useState} from 'react'; import useCancellablePromise from '../../hooks/useCancellablePromise'; import {fetchStreamData} from './fetchStreamData'; +import {MediaType} from './context/PlayerContext'; export type StreamData = { channelTitle?: string; @@ -10,6 +11,7 @@ export type StreamData = { title: string; offset?: number; poster?: string; + mediaType?: MediaType; }; type VideoState = { diff --git a/app/constants/index.ts b/app/constants/index.ts index 500387c..03f1b44 100644 --- a/app/constants/index.ts +++ b/app/constants/index.ts @@ -39,6 +39,9 @@ export const ARTICLE_TYPE_VIDEO = 6; export const ARTICLE_TYPE_AUDIO = 7; //Audio only stream covers +export const LRT_TV = 'https://www.lrt.lt/media/logo/LTV1.jpg?v=20245121947'; +export const LRT_PLUS = 'https://www.lrt.lt/media/logo/LTV2.jpg?v=20245121948'; +export const LRT_LITHUANICA = 'https://www.lrt.lt/media/logo/WORLD.jpg?v=20245121949'; export const LRT_RADIJAS = 'https://www.lrt.lt/media/logo/LR.jpg?v=2024152153'; export const LRT_KLASIKA = 'https://www.lrt.lt/media/logo/Klasika.jpg?v=2024152156'; export const LRT_OPUS = 'https://www.lrt.lt/media/logo/Opus.jpg?v=2024152157'; diff --git a/app/screens/channel/context/ChannelContextProvider.tsx b/app/screens/channel/context/ChannelContextProvider.tsx index 1acced8..fffc09f 100644 --- a/app/screens/channel/context/ChannelContextProvider.tsx +++ b/app/screens/channel/context/ChannelContextProvider.tsx @@ -4,10 +4,20 @@ import useCancellablePromise from '../../../hooks/useCancellablePromise'; import {fetchChannel, fetchVideoData} from '../../../api'; import {isVideoLiveStream} from '../../../api/Types'; import {StreamData} from '../../../components/videoComponent/useStreamData'; -import {LRT_KLASIKA, LRT_OPUS, LRT_RADIJAS} from '../../../constants'; +import {LRT_KLASIKA, LRT_LITHUANICA, LRT_OPUS, LRT_PLUS, LRT_RADIJAS, LRT_TV} from '../../../constants'; +import {MediaType} from '../../../components/videoComponent/context/PlayerContext'; const getPosterByChannelId = (channelId: string) => { switch (channelId) { + case '1': { + return LRT_TV; + } + case '2': { + return LRT_PLUS; + } + case '3': { + return LRT_LITHUANICA; + } case '5': { return LRT_KLASIKA; } @@ -42,6 +52,7 @@ const ChannelProvider: React.FC> = ({children}) => { streamUri: data.content.trim(), title: channelResponse.channel_info?.title ?? 'untitled-live-stream', mediaId: data.content, + mediaType: MediaType.VIDEO, offset: undefined, }; @@ -50,6 +61,7 @@ const ChannelProvider: React.FC> = ({children}) => { audioStreamData = { ...streamData, streamUri: data.audio.trim(), + mediaType: MediaType.AUDIO, poster: getPosterByChannelId(String(channelId)), }; } diff --git a/index.js b/index.js index 4e77e95..6b78935 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,9 @@ import 'react-native-gesture-handler'; import App from './App'; import {AppRegistry, Platform} from 'react-native'; import {name as appName} from './app.json'; +import TrackPlayer from 'react-native-track-player'; +import PlaybackService from './PlaybackService'; + // import {AndroidAutoModule} from './AndroidAuto'; if (Platform.OS === 'android') { @@ -11,3 +14,5 @@ if (Platform.OS === 'android') { } else { AppRegistry.registerComponent(appName, () => App); } + +TrackPlayer.registerPlaybackService(() => PlaybackService); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 03f7ddf..aca7f5b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1992,6 +1992,9 @@ PODS: - React-Core - THEOplayer-Integration-GoogleCast/Base (~> 7.0) - THEOplayerSDK-core (~> 7.0) + - react-native-track-player (4.1.1): + - React-Core + - SwiftAudioEx (= 1.1.0) - react-native-webview (13.8.4): - glog - RCT-Folly (= 2022.05.16.00) @@ -2224,6 +2227,7 @@ PODS: - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) - SocketRocket (0.6.1) + - SwiftAudioEx (1.1.0) - THEOplayer-Integration-GoogleCast/Base (7.1.0) - THEOplayerSDK-core (7.1.0) - Yoga (1.14.0) @@ -2270,6 +2274,7 @@ DEPENDENCIES: - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-theoplayer (from `../node_modules/react-native-theoplayer`) + - react-native-track-player (from `../node_modules/react-native-track-player`) - react-native-webview (from `../node_modules/react-native-webview`) - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -2341,6 +2346,7 @@ SPEC REPOS: - SDWebImage - SDWebImageWebPCoder - SocketRocket + - SwiftAudioEx - THEOplayer-Integration-GoogleCast - THEOplayerSDK-core @@ -2419,6 +2425,8 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-community/slider" react-native-theoplayer: :path: "../node_modules/react-native-theoplayer" + react-native-track-player: + :path: "../node_modules/react-native-track-player" react-native-webview: :path: "../node_modules/react-native-webview" React-nativeconfig: @@ -2564,6 +2572,7 @@ SPEC CHECKSUMS: react-native-safe-area-context: 0ee144a6170530ccc37a0fd9388e28d06f516a89 react-native-slider: 6a65aa2e1f1816ee80b9684ff9a07118dfab1bf6 react-native-theoplayer: fc99df4728ce32b819305736fe91136012ccced6 + react-native-track-player: 82ef1756ffeea61140fea17519ecd6d64ec3cf3e react-native-webview: 3bfbca2a3ae90e753573e5cc4c64d3ba4b5eec59 React-nativeconfig: 1c018931dcd44532a5df80426c4387d71f66c737 React-NativeModulesApple: fe9afa0c30c58c6e7f01ad34cd5012e7e42a8cad @@ -2603,6 +2612,7 @@ SPEC CHECKSUMS: SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 + SwiftAudioEx: f6aa653770f3a0d3851edaf8d834a30aee4a7646 THEOplayer-Integration-GoogleCast: 1e652a028ea70017487b80d8919bdc7a82ecd748 THEOplayerSDK-core: 7abc1738274a0fafee0b791a8ac3998c49cd9eb8 Yoga: 07db09965bc46c4902e20d3ae6990d95e53af8a8 diff --git a/ios/lrtApp.xcodeproj/project.pbxproj b/ios/lrtApp.xcodeproj/project.pbxproj index 9ad70f3..01df1bd 100644 --- a/ios/lrtApp.xcodeproj/project.pbxproj +++ b/ios/lrtApp.xcodeproj/project.pbxproj @@ -783,13 +783,13 @@ CODE_SIGN_ENTITLEMENTS = lrtApp/lrtApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 98; + CURRENT_PROJECT_VERSION = 100; DEVELOPMENT_TEAM = TGMUP98888; ENABLE_BITCODE = NO; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; INFOPLIST_FILE = lrtApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.41.2; + MARKETING_VERSION = 2.41.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -817,11 +817,11 @@ CODE_SIGN_ENTITLEMENTS = lrtApp/lrtAppRelease.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 98; + CURRENT_PROJECT_VERSION = 100; DEVELOPMENT_TEAM = TGMUP98888; INFOPLIST_FILE = lrtApp/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 2.41.2; + MARKETING_VERSION = 2.41.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/ios/lrtApp/CarScene.swift b/ios/lrtApp/CarScene.swift index a55c8b9..efb69fb 100644 --- a/ios/lrtApp/CarScene.swift +++ b/ios/lrtApp/CarScene.swift @@ -1,13 +1,22 @@ -import Foundation import CarPlay +import Foundation class CarSceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate { - func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, - didConnect interfaceController: CPInterfaceController) { - RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow); + func templateApplicationScene( + _ templateApplicationScene: CPTemplateApplicationScene, + didConnect interfaceController: CPInterfaceController + ) { + guard let appDelegate = (UIApplication.shared.delegate as? AppDelegate) else { return } + appDelegate.initAppFromScene(connectionOptions: nil) + + RNCarPlay.connect(with: interfaceController, window: templateApplicationScene.carWindow) + } - func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene, didDisconnectInterfaceController interfaceController: CPInterfaceController) { + func templateApplicationScene( + _ templateApplicationScene: CPTemplateApplicationScene, + didDisconnectInterfaceController interfaceController: CPInterfaceController + ) { RNCarPlay.disconnect() } } diff --git a/package.json b/package.json index 379069c..123e911 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "react-native-svg": "^14.1.0", "react-native-tab-view": "^3.5.2", "react-native-theoplayer": "^7.0.0", + "react-native-track-player": "^4.1.1", "react-native-webview": "13.8.4", "react-navigation-collapsible": "6.2.0", "react-redux": "^8.1.3", @@ -69,6 +70,7 @@ "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", + "@react-native/babel-preset": "0.73.21", "@react-native/eslint-config": "0.73.2", "@react-native/metro-config": "0.73.5", "@react-native/typescript-config": "0.73.1", @@ -82,7 +84,6 @@ "eslint": "^8.19.0", "eslint-plugin-prettier": "^3.4.0", "jest": "^29.6.3", - "@react-native/babel-preset": "0.73.21", "prettier": "2.8.8", "react-native-svg-app-icon": "^0.6.1", "react-test-renderer": "18.2.0", From f9068df361f3761f7046f7b2fdcecd889f2401c0 Mon Sep 17 00:00:00 2001 From: Kestas Venslauskas Date: Thu, 16 May 2024 21:14:42 +0300 Subject: [PATCH 2/4] feat: car play media tracking --- PlaybackService.ts | 62 ++++++++++- app/car/newest/useCarNewestTemplate.ts | 2 +- app/car/popular/useCarPopularTemplate.ts | 2 +- .../recommended/useCarRecommendedTemplate.ts | 2 +- .../videoComponent/useMediaTracking.ts | 105 +++++++----------- 5 files changed, 101 insertions(+), 72 deletions(-) diff --git a/PlaybackService.ts b/PlaybackService.ts index 28b8b57..e357019 100644 --- a/PlaybackService.ts +++ b/PlaybackService.ts @@ -1,4 +1,6 @@ -import TrackPlayer, {Event} from 'react-native-track-player'; +import TrackPlayer, {Event, PlaybackState, State} from 'react-native-track-player'; +import {tracker} from './app/components/videoComponent/useMediaTracking'; +import Gemius from 'react-native-gemius-plugin'; const PlaybackService = async () => { TrackPlayer.addEventListener(Event.RemotePause, () => { @@ -23,15 +25,38 @@ const PlaybackService = async () => { }); TrackPlayer.addEventListener(Event.RemoteJumpForward, async (event) => { console.log('Event.RemoteJumpForward', event); - TrackPlayer.seekBy(event.interval); + await TrackPlayer.seekBy(event.interval); + + const activeTrack = await TrackPlayer.getActiveTrack(); + if (!activeTrack) { + console.warn('No active track'); + return; + } + const progress = await TrackPlayer.getProgress(); + tracker.trackSeek(activeTrack.url, progress.position); }); TrackPlayer.addEventListener(Event.RemoteJumpBackward, async (event) => { console.log('Event.RemoteJumpBackward', event); - TrackPlayer.seekBy(-event.interval); + await TrackPlayer.seekBy(-event.interval); + + const activeTrack = await TrackPlayer.getActiveTrack(); + if (!activeTrack) { + console.warn('No active track'); + return; + } + const progress = await TrackPlayer.getProgress(); + tracker.trackSeek(activeTrack.url, progress.position); }); - TrackPlayer.addEventListener(Event.RemoteSeek, (event) => { + TrackPlayer.addEventListener(Event.RemoteSeek, async (event) => { console.log('Event.RemoteSeek', event); TrackPlayer.seekTo(event.position); + + const activeTrack = await TrackPlayer.getActiveTrack(); + if (!activeTrack) { + console.log('PlaybackService: no active track. Skipping analytics tracking.'); + return; + } + tracker.trackSeek(activeTrack.url, event.position); }); // TrackPlayer.addEventListener(Event.RemoteDuck, async (event) => { // console.log('Event.RemoteDuck', event); @@ -48,8 +73,35 @@ const PlaybackService = async () => { // TrackPlayer.addEventListener(Event.PlaybackPlayWhenReadyChanged, (event) => { // console.log('Event.PlaybackPlayWhenReadyChanged', event); // }); - TrackPlayer.addEventListener(Event.PlaybackState, (event) => { + TrackPlayer.addEventListener(Event.PlaybackState, async (event) => { console.log('Event.PlaybackState', event); + const activeTrack = await TrackPlayer.getActiveTrack(); + if (!activeTrack) { + console.log('PlaybackService: no active track. Skipping analytics tracking.'); + return; + } + + const progress = await TrackPlayer.getProgress(); + switch (event.state) { + case State.Ready: + Gemius.setProgramData(activeTrack.url, activeTrack.title ?? '', progress.duration, false); + break; + case State.Playing: + tracker.trackPlay(activeTrack.url, progress.position); + break; + case State.Paused: + tracker.trackPause(activeTrack.url, progress.position); + break; + case State.Stopped: + tracker.trackClose(activeTrack.url, progress.position); + break; + case State.Buffering: + tracker.trackBuffer(activeTrack.url, progress.position); + break; + case State.Ended: + tracker.trackComplete(activeTrack.url, progress.position); + break; + } }); // TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, (event) => { // console.log('[Deprecated] Event.PlaybackMetadataReceived', event); diff --git a/app/car/newest/useCarNewestTemplate.ts b/app/car/newest/useCarNewestTemplate.ts index 98c8e4f..3b385c3 100644 --- a/app/car/newest/useCarNewestTemplate.ts +++ b/app/car/newest/useCarNewestTemplate.ts @@ -29,7 +29,7 @@ const useCarNewestTemplate = (isConnected: boolean) => { channels.map((item) => ({ uri: item.streamUrl, mediaType: MediaType.AUDIO, - isLiveStream: true, + isLiveStream: false, poster: item.imgUrl, title: item.text, })), diff --git a/app/car/popular/useCarPopularTemplate.ts b/app/car/popular/useCarPopularTemplate.ts index 093efa0..115e41e 100644 --- a/app/car/popular/useCarPopularTemplate.ts +++ b/app/car/popular/useCarPopularTemplate.ts @@ -29,7 +29,7 @@ const useCarPopularTemplate = (isConnected: boolean) => { channels.map((item) => ({ uri: item.streamUrl, mediaType: MediaType.AUDIO, - isLiveStream: true, + isLiveStream: false, poster: item.imgUrl, title: item.text, })), diff --git a/app/car/recommended/useCarRecommendedTemplate.ts b/app/car/recommended/useCarRecommendedTemplate.ts index b4cad2b..8e45e55 100644 --- a/app/car/recommended/useCarRecommendedTemplate.ts +++ b/app/car/recommended/useCarRecommendedTemplate.ts @@ -29,7 +29,7 @@ const useCarRecommendedTemplate = (isConnected: boolean) => { channels.map((item) => ({ uri: item.streamUrl, mediaType: MediaType.AUDIO, - isLiveStream: true, + isLiveStream: false, poster: item.imgUrl, title: item.text, })), diff --git a/app/components/videoComponent/useMediaTracking.ts b/app/components/videoComponent/useMediaTracking.ts index b3bcca5..d05eeac 100644 --- a/app/components/videoComponent/useMediaTracking.ts +++ b/app/components/videoComponent/useMediaTracking.ts @@ -1,8 +1,7 @@ import {debounce} from 'lodash'; -import {useMemo} from 'react'; import Gemius from 'react-native-gemius-plugin'; -type ReturnType = { +type MediaAnalyticsTracker = { trackPlay: (mediaId: string, time: number) => void; trackPause: (mediaId: string, time: number) => void; trackClose: (mediaId: string, time: number) => void; @@ -13,69 +12,47 @@ type ReturnType = { const EVENT_DEBOUNCE_DURATION = 200; -const useMediaTracking = (): ReturnType => { - const sendPlay = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: play', time); - Gemius.sendPlay(mediaId, time ? time : 0); - }, EVENT_DEBOUNCE_DURATION), - [], - ); - - const sendPause = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: pause', time); - Gemius.sendPause(mediaId, time ? time : 0); - }, EVENT_DEBOUNCE_DURATION), - [], - ); - - const sendClose = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: close, time'); - Gemius.sendClose(mediaId, time ? time : 0); - }, EVENT_DEBOUNCE_DURATION), - [], - ); - - const sendBuffer = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: buffering'); - Gemius.sendBuffer(mediaId, time ? time : 0); - }, EVENT_DEBOUNCE_DURATION), - [], - ); - - const sendComplete = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: complete'); - Gemius.sendComplete(mediaId, time ? time : 0); - }, EVENT_DEBOUNCE_DURATION), - [], - ); - - const sendSeek = useMemo( - () => - debounce((mediaId: string, time: number) => { - console.log('MediaPlayer event: seek ' + time); - Gemius.sendSeek(mediaId, time); - }, EVENT_DEBOUNCE_DURATION), - [], - ); +const trackPlay = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: play', time); + Gemius.sendPlay(mediaId, time ? time : 0); +}, EVENT_DEBOUNCE_DURATION); + +const trackPause = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: pause', time); + Gemius.sendPause(mediaId, time ? time : 0); +}, EVENT_DEBOUNCE_DURATION); + +const trackClose = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: close', time); + Gemius.sendClose(mediaId, time ? time : 0); +}, EVENT_DEBOUNCE_DURATION); + +const trackBuffer = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: buffering'); + Gemius.sendBuffer(mediaId, time ? time : 0); +}, EVENT_DEBOUNCE_DURATION); + +const trackComplete = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: complete'); + Gemius.sendComplete(mediaId, time ? time : 0); +}, EVENT_DEBOUNCE_DURATION); + +const trackSeek = debounce((mediaId: string, time: number) => { + console.log('MediaAnalyticsTracker event: seek ' + time); + Gemius.sendSeek(mediaId, time); +}, EVENT_DEBOUNCE_DURATION); + +export const tracker: MediaAnalyticsTracker = { + trackPlay, + trackPause, + trackClose, + trackBuffer, + trackComplete, + trackSeek, +}; - return { - trackPlay: sendPlay, - trackPause: sendPause, - trackSeek: sendSeek, - trackBuffer: sendBuffer, - trackClose: sendClose, - trackComplete: sendComplete, - }; +const useMediaTracking = (): MediaAnalyticsTracker => { + return tracker; }; export default useMediaTracking; From be6a9e18632d9e40f3f4558dfe628eff71fab4e3 Mon Sep 17 00:00:00 2001 From: Kestas Venslauskas Date: Thu, 16 May 2024 21:23:13 +0300 Subject: [PATCH 3/4] chore: sonar fixes --- PlaybackService.ts | 43 +------------------ app/api/index.ts | 1 - .../videoComponent/TheoMediaPlayer.tsx | 2 +- .../videoComponent/VideoComponent.tsx | 8 +--- .../videoComponent/context/PlayerContext.ts | 2 +- .../videoComponent/fetchStreamData.ts | 6 +-- 6 files changed, 5 insertions(+), 57 deletions(-) diff --git a/PlaybackService.ts b/PlaybackService.ts index e357019..357b510 100644 --- a/PlaybackService.ts +++ b/PlaybackService.ts @@ -1,4 +1,4 @@ -import TrackPlayer, {Event, PlaybackState, State} from 'react-native-track-player'; +import TrackPlayer, {Event, State} from 'react-native-track-player'; import {tracker} from './app/components/videoComponent/useMediaTracking'; import Gemius from 'react-native-gemius-plugin'; @@ -58,21 +58,6 @@ const PlaybackService = async () => { } tracker.trackSeek(activeTrack.url, event.position); }); - // TrackPlayer.addEventListener(Event.RemoteDuck, async (event) => { - // console.log('Event.RemoteDuck', event); - // }); - // TrackPlayer.addEventListener(Event.PlaybackQueueEnded, (event) => { - // console.log('Event.PlaybackQueueEnded', event); - // }); - // TrackPlayer.addEventListener(Event.PlaybackActiveTrackChanged, (event) => { - // console.log('Event.PlaybackActiveTrackChanged', event); - // }); - // TrackPlayer.addEventListener(Event.PlaybackProgressUpdated, (event) => { - // console.log('Event.PlaybackProgressUpdated', event); - // }); - // TrackPlayer.addEventListener(Event.PlaybackPlayWhenReadyChanged, (event) => { - // console.log('Event.PlaybackPlayWhenReadyChanged', event); - // }); TrackPlayer.addEventListener(Event.PlaybackState, async (event) => { console.log('Event.PlaybackState', event); const activeTrack = await TrackPlayer.getActiveTrack(); @@ -103,32 +88,6 @@ const PlaybackService = async () => { break; } }); - // TrackPlayer.addEventListener(Event.PlaybackMetadataReceived, (event) => { - // console.log('[Deprecated] Event.PlaybackMetadataReceived', event); - // }); - // TrackPlayer.addEventListener(Event.MetadataChapterReceived, (event) => { - // console.log('Event.MetadataChapterReceived', event); - // }); - // TrackPlayer.addEventListener(Event.MetadataTimedReceived, (event) => { - // console.log('Event.MetadataTimedReceived', event); - // }); - // TrackPlayer.addEventListener(Event.MetadataCommonReceived, (event) => { - // console.log('Event.MetadataCommonReceived', event); - // }); - // TrackPlayer.addEventListener(Event.PlaybackProgressUpdated, (event) => { - // console.log('Event.PlaybackProgressUpdated', event); - // }); - // TrackPlayer.addEventListener(Event.MetadataCommonReceived, async ({metadata}) => { - // const activeTrack = await TrackPlayer.getActiveTrack(); - // console.log('Event.MetadataCommonReceived', metadata, activeTrack); - // TrackPlayer.updateNowPlayingMetadata({ - // artist: [metadata.title, metadata.artist].filter(Boolean).join(' - '), - // title: activeTrack?.title ?? metadata.title, - // artwork: activeTrack?.artwork ?? metadata.artworkUri, - // duration: activeTrack?.duration, - // isLiveStream: activeTrack?.isLiveStream, - // }); - // }); }; export default PlaybackService; diff --git a/app/api/index.ts b/app/api/index.ts index 457f065..b6353b6 100644 --- a/app/api/index.ts +++ b/app/api/index.ts @@ -47,7 +47,6 @@ import { SearchFilter, SearchResponse, SlugArticlesResponse, - TVProgramChannel, TVProgramResponse, VideoDataDefault, VideoDataLiveStream, diff --git a/app/components/videoComponent/TheoMediaPlayer.tsx b/app/components/videoComponent/TheoMediaPlayer.tsx index 8d132e9..14be586 100644 --- a/app/components/videoComponent/TheoMediaPlayer.tsx +++ b/app/components/videoComponent/TheoMediaPlayer.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useEffect, useState} from 'react'; -import {ActivityIndicator, BackHandler, ImageBackground, Platform, StyleSheet, View} from 'react-native'; +import {ActivityIndicator, BackHandler, Platform, StyleSheet, View} from 'react-native'; import { PlayerConfiguration, SourceDescription, diff --git a/app/components/videoComponent/VideoComponent.tsx b/app/components/videoComponent/VideoComponent.tsx index 06b31de..eab9dbc 100644 --- a/app/components/videoComponent/VideoComponent.tsx +++ b/app/components/videoComponent/VideoComponent.tsx @@ -105,13 +105,7 @@ const VideoComponent: React.FC> = (props) => { isLiveStream={data.isLiveStream} startTime={data.isLiveStream ? undefined : props.startTime || data.offset} poster={props.backgroundImage ?? data.poster} - mediaType={ - data.mediaType != undefined - ? data.mediaType - : data.streamUri.includes('audio') - ? MediaType.AUDIO - : MediaType.VIDEO - } + mediaType={data.mediaType ?? data.streamUri.includes('audio') ? MediaType.AUDIO : MediaType.VIDEO} onError={onPlayerError} /> diff --git a/app/components/videoComponent/context/PlayerContext.ts b/app/components/videoComponent/context/PlayerContext.ts index 0640447..8779607 100644 --- a/app/components/videoComponent/context/PlayerContext.ts +++ b/app/components/videoComponent/context/PlayerContext.ts @@ -17,7 +17,7 @@ export type MediaBaseData = { }; export type PlayerContextType = { - setPlaylist: (data: MediaBaseData[], current?: number) => void; + setPlaylist: (data: MediaBaseData[], current?: number) => Promise; playNext: () => void; playPrevious: () => void; close: () => void; diff --git a/app/components/videoComponent/fetchStreamData.ts b/app/components/videoComponent/fetchStreamData.ts index 976cb3c..12fa706 100644 --- a/app/components/videoComponent/fetchStreamData.ts +++ b/app/components/videoComponent/fetchStreamData.ts @@ -21,11 +21,7 @@ export const fetchStreamData = ({ return { channelTitle: title, isLiveStream: true, - streamUri: prioritizeAudio - ? data.audio - ? data.audio.trim() - : data.content.trim() - : data.content.trim(), + streamUri: prioritizeAudio ? data.audio?.trim() ?? data.content.trim() : data.content.trim(), title: title ?? 'untitled-live-stream', poster: poster, mediaId: data.content, From c8f48bac562a316e4d546fe06483246cf60c8fe5 Mon Sep 17 00:00:00 2001 From: Kestas Venslauskas Date: Thu, 16 May 2024 21:26:27 +0300 Subject: [PATCH 4/4] fix: media type bug --- app/components/videoComponent/VideoComponent.tsx | 8 +++++++- app/components/videoComponent/fetchStreamData.ts | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/components/videoComponent/VideoComponent.tsx b/app/components/videoComponent/VideoComponent.tsx index eab9dbc..06b31de 100644 --- a/app/components/videoComponent/VideoComponent.tsx +++ b/app/components/videoComponent/VideoComponent.tsx @@ -105,7 +105,13 @@ const VideoComponent: React.FC> = (props) => { isLiveStream={data.isLiveStream} startTime={data.isLiveStream ? undefined : props.startTime || data.offset} poster={props.backgroundImage ?? data.poster} - mediaType={data.mediaType ?? data.streamUri.includes('audio') ? MediaType.AUDIO : MediaType.VIDEO} + mediaType={ + data.mediaType != undefined + ? data.mediaType + : data.streamUri.includes('audio') + ? MediaType.AUDIO + : MediaType.VIDEO + } onError={onPlayerError} /> diff --git a/app/components/videoComponent/fetchStreamData.ts b/app/components/videoComponent/fetchStreamData.ts index 12fa706..976cb3c 100644 --- a/app/components/videoComponent/fetchStreamData.ts +++ b/app/components/videoComponent/fetchStreamData.ts @@ -21,7 +21,11 @@ export const fetchStreamData = ({ return { channelTitle: title, isLiveStream: true, - streamUri: prioritizeAudio ? data.audio?.trim() ?? data.content.trim() : data.content.trim(), + streamUri: prioritizeAudio + ? data.audio + ? data.audio.trim() + : data.content.trim() + : data.content.trim(), title: title ?? 'untitled-live-stream', poster: poster, mediaId: data.content,