From da14ddeb66016835c42bb79e5771d44f737718a8 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Thu, 22 Feb 2024 10:40:10 +0100 Subject: [PATCH 01/46] Make chromecast button's connected state depend only on chromecast state and not casting state in general (i.e. airplay). --- src/ui/components/button/ChromecastButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/button/ChromecastButton.tsx b/src/ui/components/button/ChromecastButton.tsx index af4c798..4b54477 100644 --- a/src/ui/components/button/ChromecastButton.tsx +++ b/src/ui/components/button/ChromecastButton.tsx @@ -37,7 +37,7 @@ export class ChromecastButton extends PureComponent Date: Thu, 22 Feb 2024 10:42:41 +0100 Subject: [PATCH 02/46] Add changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2df671..22d3c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Fixed connected state for chromecastButton to not take into account the casting state in general (e.g. airplay should not influence this state). + ## [0.4.0] - 24-02-14 ### Added From b81c1293e89f0d4e157083443ce499707f8cf1d6 Mon Sep 17 00:00:00 2001 From: Tom Van Laerhoven Date: Thu, 22 Feb 2024 10:53:56 +0100 Subject: [PATCH 03/46] Fix boolean type --- src/ui/components/button/ChromecastButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/button/ChromecastButton.tsx b/src/ui/components/button/ChromecastButton.tsx index 4b54477..6ff7cb8 100644 --- a/src/ui/components/button/ChromecastButton.tsx +++ b/src/ui/components/button/ChromecastButton.tsx @@ -37,7 +37,7 @@ export class ChromecastButton extends PureComponent Date: Thu, 22 Feb 2024 10:40:10 +0100 Subject: [PATCH 04/46] Make chromecast button's connected state depend only on chromecast state and not casting state in general (i.e. airplay). --- src/ui/components/button/ChromecastButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/button/ChromecastButton.tsx b/src/ui/components/button/ChromecastButton.tsx index af4c798..499bcfa 100644 --- a/src/ui/components/button/ChromecastButton.tsx +++ b/src/ui/components/button/ChromecastButton.tsx @@ -37,7 +37,7 @@ export class ChromecastButton extends PureComponent Date: Thu, 22 Feb 2024 10:42:41 +0100 Subject: [PATCH 05/46] Add changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2df671..22d3c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Fixed connected state for chromecastButton to not take into account the casting state in general (e.g. airplay should not influence this state). + ## [0.4.0] - 24-02-14 ### Added From 36802ce51ca24af1c8e2a0a2a008c32f7bb0e7db Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 1 Feb 2024 14:31:25 +0100 Subject: [PATCH 06/46] Detect react version for eslint --- .eslintrc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.eslintrc b/.eslintrc index 81c62b0..54ffbad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -17,5 +17,10 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }] + }, + "settings": { + "react": { + "version": "detect" + } } } From da0448cd508ffe7ebddbd8af78d03dc83b0402c5 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 1 Feb 2024 14:31:00 +0100 Subject: [PATCH 07/46] Improve SeekBar during ads --- src/ui/components/seekbar/SeekBar.tsx | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ui/components/seekbar/SeekBar.tsx b/src/ui/components/seekbar/SeekBar.tsx index e448214..5874701 100644 --- a/src/ui/components/seekbar/SeekBar.tsx +++ b/src/ui/components/seekbar/SeekBar.tsx @@ -1,6 +1,15 @@ import React, { PureComponent } from 'react'; import { LayoutChangeEvent, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; -import { DurationChangeEvent, LoadedMetadataEvent, PlayerEventType, ProgressEvent, TimeRange, TimeUpdateEvent } from 'react-native-theoplayer'; +import { + AdEvent, + AdEventType, + DurationChangeEvent, + LoadedMetadataEvent, + PlayerEventType, + ProgressEvent, + TimeRange, + TimeUpdateEvent, +} from 'react-native-theoplayer'; import { PlayerContext, UiContext } from '../util/PlayerContext'; import Slider from '@react-native-community/slider'; import { SingleThumbnailView } from './thumbnail/SingleThumbnailView'; @@ -20,6 +29,7 @@ interface SeekBarState { duration: number; sliderTime: number; width: number; + playingAd: boolean; } /** @@ -34,6 +44,7 @@ export class SeekBar extends PureComponent { seekable: [], pausedDueToScrubbing: false, width: 0, + playingAd: false, }; private _seekBlockingTimeout: NodeJS.Timeout | undefined; @@ -50,6 +61,7 @@ export class SeekBar extends PureComponent { player.addEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); player.addEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); player.addEventListener(PlayerEventType.PROGRESS, this._onProgress); + player.addEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); this.setState({ ...SeekBar.initialState, sliderTime: player.currentTime, @@ -64,6 +76,7 @@ export class SeekBar extends PureComponent { context.player.removeEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); context.player.removeEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); context.player.removeEventListener(PlayerEventType.PROGRESS, this._onProgress); + context.player.removeEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); clearTimeout(this._seekBlockingTimeout); clearTimeout(this._clearIsScrubbingTimout); } @@ -77,6 +90,14 @@ export class SeekBar extends PureComponent { private _onDurationChange = (event: DurationChangeEvent) => this.setState({ duration: event.duration }); private _onProgress = (event: ProgressEvent) => this.setState({ seekable: event.seekable }); + private _onAdEvent = (event: AdEvent) => { + if (event.subType === AdEventType.AD_BREAK_BEGIN) { + this.setState({ playingAd: true }); + } else if (event.subType === AdEventType.AD_BREAK_END) { + this.setState({ playingAd: false }); + } + }; + private _onSlidingStart = (value: number) => { this.setState({ sliderTime: value }); this._prepareSeekStart(); @@ -138,7 +159,7 @@ export class SeekBar extends PureComponent { }; render() { - const { seekable, sliderTime, duration, isSeeking, width } = this.state; + const { seekable, sliderTime, duration, isSeeking, width, playingAd } = this.state; const { style } = this.props; const seekableStart = seekable.length > 0 ? seekable[0].start : 0; const seekableEnd = seekable.length > 0 ? seekable[0].end : 0; @@ -154,10 +175,10 @@ export class SeekBar extends PureComponent { )} 0) && seekable.length > 0} + disabled={(!(duration > 0) && seekable.length > 0) || playingAd} style={[StyleSheet.absoluteFill, style]} minimumValue={seekableStart} - maximumValue={seekableEnd} + maximumValue={playingAd ? duration : seekableEnd} step={1000} onSlidingStart={this._onSlidingStart} onValueChange={this._onValueChange} From e0d7c324ed9d4bc2f9e32a26a0d05cc8acdd6bb2 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 1 Feb 2024 14:38:58 +0100 Subject: [PATCH 08/46] Simplify --- src/ui/components/seekbar/SeekBar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ui/components/seekbar/SeekBar.tsx b/src/ui/components/seekbar/SeekBar.tsx index 5874701..7d805bc 100644 --- a/src/ui/components/seekbar/SeekBar.tsx +++ b/src/ui/components/seekbar/SeekBar.tsx @@ -71,12 +71,12 @@ export class SeekBar extends PureComponent { } componentWillUnmount() { - const context = this.context as UiContext; - context.player.removeEventListener(PlayerEventType.LOADED_METADATA, this._onLoadedMetadata); - context.player.removeEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); - context.player.removeEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); - context.player.removeEventListener(PlayerEventType.PROGRESS, this._onProgress); - context.player.removeEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); + const player = (this.context as UiContext).player; + player.removeEventListener(PlayerEventType.LOADED_METADATA, this._onLoadedMetadata); + player.removeEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); + player.removeEventListener(PlayerEventType.PROGRESS, this._onProgress); + player.removeEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); clearTimeout(this._seekBlockingTimeout); clearTimeout(this._clearIsScrubbingTimout); } From 32ec60cf554f1f27e2ef058da50b78592d5f8e4a Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 1 Feb 2024 17:10:47 +0100 Subject: [PATCH 09/46] Add AdDisplay component --- src/ui/components/ads/AdDisplay.tsx | 80 +++++++++++++++++++++++++++++ src/ui/components/ads/barrel.ts | 1 + src/ui/components/barrel.ts | 1 + 3 files changed, 82 insertions(+) create mode 100644 src/ui/components/ads/AdDisplay.tsx create mode 100644 src/ui/components/ads/barrel.ts diff --git a/src/ui/components/ads/AdDisplay.tsx b/src/ui/components/ads/AdDisplay.tsx new file mode 100644 index 0000000..ed8f7f3 --- /dev/null +++ b/src/ui/components/ads/AdDisplay.tsx @@ -0,0 +1,80 @@ +import React, { PureComponent } from 'react'; +import { PlayerContext, UiContext } from '../util/PlayerContext'; +import { StyleProp, Text, TextStyle } from 'react-native'; +import { AdEvent, AdEventType, PlayerEventType } from 'react-native-theoplayer'; + +interface AdDisplayProps { + /** + * Optional style applied to the AdDisplay + */ + style?: StyleProp; +} + +interface AdDisplayState { + adPlaying: boolean; + currentAd: number | undefined; + totalAds: number | undefined; +} + +export class AdDisplay extends PureComponent { + private static initialState = { + adPlaying: false, + currentAd: undefined, + totalAds: undefined, + }; + + constructor(props: AdDisplayProps) { + super(props); + this.state = AdDisplay.initialState; + } + + componentDidMount() { + const player = (this.context as UiContext).player; + player.addEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + componentWillUnmount() { + const player = (this.context as UiContext).player; + player.removeEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + private onAdEvent = (event: AdEvent) => { + void this.updateAdDisplayState(event.subType); + }; + + private async updateAdDisplayState(type: AdEventType) { + const { adPlaying, currentAd } = this.state; + + if (type === AdEventType.AD_BREAK_BEGIN && !adPlaying) { + const player = (this.context as UiContext).player; + const currentAdBreak = await player.ads.currentAdBreak(); + const currentAds = currentAdBreak.ads; + if (currentAds) { + this.setState({ adPlaying: true, currentAd: 0, totalAds: currentAds.length }); + } + } else if (type === AdEventType.AD_BREAK_END) { + this.setState(AdDisplay.initialState); + } else if (type === AdEventType.AD_BEGIN) { + const newCurrentAd = currentAd ? currentAd + 1 : 1; + this.setState({ currentAd: newCurrentAd }); + } + } + + render() { + const { adPlaying, currentAd, totalAds } = this.state; + const { style } = this.props; + if (!adPlaying || totalAds === undefined) { + return <>; + } + + const label = totalAds > 1 ? `Ad ${currentAd} of ${totalAds}` : 'Ad'; + + return ( + + {(context: UiContext) => {label}} + + ); + } +} + +AdDisplay.contextType = PlayerContext; diff --git a/src/ui/components/ads/barrel.ts b/src/ui/components/ads/barrel.ts new file mode 100644 index 0000000..021a680 --- /dev/null +++ b/src/ui/components/ads/barrel.ts @@ -0,0 +1 @@ +export * from './AdDisplay'; diff --git a/src/ui/components/barrel.ts b/src/ui/components/barrel.ts index fa6f4cf..76b3ce2 100644 --- a/src/ui/components/barrel.ts +++ b/src/ui/components/barrel.ts @@ -1,4 +1,5 @@ export * from './activityindicator/barrel'; +export * from './ads/barrel'; export * from './button/barrel'; export * from './controlbar/barrel'; export * from './menu/barrel'; From e1cb7e5216efd11fd2bc02fb1aa64f2e633467ea Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 2 Feb 2024 14:31:01 +0100 Subject: [PATCH 10/46] Add AdCountdown component --- src/ui/components/ads/AdCountdown.tsx | 77 +++++++++++++++++++++++++++ src/ui/components/ads/barrel.ts | 1 + 2 files changed, 78 insertions(+) create mode 100644 src/ui/components/ads/AdCountdown.tsx diff --git a/src/ui/components/ads/AdCountdown.tsx b/src/ui/components/ads/AdCountdown.tsx new file mode 100644 index 0000000..5d5c668 --- /dev/null +++ b/src/ui/components/ads/AdCountdown.tsx @@ -0,0 +1,77 @@ +import React, { PureComponent } from 'react'; +import { PlayerContext, UiContext } from '../util/PlayerContext'; +import { StyleProp, Text, TextStyle } from 'react-native'; +import { AdEvent, AdEventType, PlayerEventType, TimeUpdateEvent } from 'react-native-theoplayer'; + +interface AdCountdownProps { + /** + * Optional style applied to the AdCountdown + */ + style?: StyleProp; +} + +interface AdCountdownState { + maxRemainingDuration: number | undefined; +} + +export class AdCountdown extends PureComponent { + private static initialState = { + maxRemainingDuration: undefined, + }; + + constructor(props: AdCountdownProps) { + super(props); + this.state = AdCountdown.initialState; + } + + componentDidMount() { + const player = (this.context as UiContext).player; + player.addEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + componentWillUnmount() { + const player = (this.context as UiContext).player; + player.removeEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } + + private onAdEvent = (event: AdEvent) => { + const player = (this.context as UiContext).player; + if (event.subType === AdEventType.AD_BREAK_BEGIN) { + void this.update(); + player.addEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } else if (event.subType === AdEventType.AD_BREAK_END) { + this.setState(AdCountdown.initialState); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } + }; + + private onTimeUpdateEvent = (_event: TimeUpdateEvent) => { + void this.update(); + }; + + private async update() { + const player = (this.context as UiContext).player; + const currentAdBreak = await player.ads.currentAdBreak(); + const maxRemainingDuration = currentAdBreak?.maxRemainingDuration; + this.setState({ maxRemainingDuration: maxRemainingDuration }); + } + + render() { + const { maxRemainingDuration } = this.state; + const { style } = this.props; + if (maxRemainingDuration === undefined || isNaN(maxRemainingDuration) || maxRemainingDuration < 0) { + return <>; + } + + const label = `Content will resume in ${Math.ceil(maxRemainingDuration)}s`; + + return ( + + {(context: UiContext) => {label}} + + ); + } +} + +AdCountdown.contextType = PlayerContext; diff --git a/src/ui/components/ads/barrel.ts b/src/ui/components/ads/barrel.ts index 021a680..de42147 100644 --- a/src/ui/components/ads/barrel.ts +++ b/src/ui/components/ads/barrel.ts @@ -1 +1,2 @@ +export * from './AdCountdown'; export * from './AdDisplay'; From 57035935329622c2015166692b82f88b15e58dec Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 5 Feb 2024 10:51:26 +0100 Subject: [PATCH 11/46] Add AdSkipButton --- src/ui/components/ads/AdSkipButton.tsx | 101 +++++++++++++++++++++++++ src/ui/components/ads/barrel.ts | 1 + src/ui/utils/AdUtils.ts | 5 ++ src/ui/utils/ArrayUtils.ts | 12 +++ 4 files changed, 119 insertions(+) create mode 100644 src/ui/components/ads/AdSkipButton.tsx create mode 100644 src/ui/utils/AdUtils.ts diff --git a/src/ui/components/ads/AdSkipButton.tsx b/src/ui/components/ads/AdSkipButton.tsx new file mode 100644 index 0000000..dedd5ed --- /dev/null +++ b/src/ui/components/ads/AdSkipButton.tsx @@ -0,0 +1,101 @@ +import React, { PureComponent } from 'react'; +import { PlayerContext, UiContext } from '../util/PlayerContext'; +import { Button, StyleProp, Text, TextStyle } from 'react-native'; +import { Ad, AdEvent, AdEventType, PlayerEventType, TimeUpdateEvent } from 'react-native-theoplayer'; +import { arrayFind } from '../../utils/ArrayUtils'; +import { isLinearAd } from '../../utils/AdUtils'; + +interface AdSkipButtonProps { + /** + * Optional style applied to the AdSkipButton + */ + style?: StyleProp; +} + +interface AdSkipButtonState { + currentAd: Ad | undefined; + timeToSkip: number | undefined; +} + +export class AdSkipButton extends PureComponent { + private static initialState = { + currentAd: undefined, + timeToSkip: undefined, + }; + + constructor(props: AdSkipButtonProps) { + super(props); + this.state = AdSkipButton.initialState; + } + + componentDidMount() { + const player = (this.context as UiContext).player; + player.addEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + componentWillUnmount() { + const player = (this.context as UiContext).player; + player.removeEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } + + private onAdEvent = (event: AdEvent) => { + const player = (this.context as UiContext).player; + if (event.subType === AdEventType.AD_BREAK_BEGIN) { + void this.update(); + player.addEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } else if (event.subType === AdEventType.AD_BREAK_END) { + this.setState(AdSkipButton.initialState); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } + }; + + private onTimeUpdateEvent = (_event: TimeUpdateEvent) => { + void this.update(); + }; + + private async update() { + const player = (this.context as UiContext).player; + const currentAds = await player.ads.currentAds(); + const linearAd = arrayFind(currentAds ?? [], isLinearAd); + const skipOffset = linearAd?.skipOffset; + if (skipOffset === undefined || skipOffset < 0) { + this.setState({ currentAd: linearAd, timeToSkip: undefined }); + } else { + const timeToSkip = Math.ceil(skipOffset - player.currentTime / 1000); + this.setState({ currentAd: linearAd, timeToSkip: timeToSkip }); + } + } + + private onPress = () => { + const player = (this.context as UiContext).player; + player.ads.skip(); + }; + + render() { + const { currentAd, timeToSkip } = this.state; + const { style } = this.props; + if (timeToSkip === undefined || isNaN(timeToSkip)) { + return <>; + } + + if (currentAd && currentAd.integration === 'google-ima') { + return <>; + } + + if (timeToSkip > 0) { + const label = `Skip in ${Math.ceil(timeToSkip)}s`; + return ( + + {(context: UiContext) => {label}} + + ); + } + + return ( + {(_context: UiContext) => } + ); + } +} + +AdSkipButton.contextType = PlayerContext; diff --git a/src/ui/components/ads/barrel.ts b/src/ui/components/ads/barrel.ts index de42147..ad5e43e 100644 --- a/src/ui/components/ads/barrel.ts +++ b/src/ui/components/ads/barrel.ts @@ -1,2 +1,3 @@ export * from './AdCountdown'; export * from './AdDisplay'; +export * from './AdSkipButton'; diff --git a/src/ui/utils/AdUtils.ts b/src/ui/utils/AdUtils.ts new file mode 100644 index 0000000..3a474dd --- /dev/null +++ b/src/ui/utils/AdUtils.ts @@ -0,0 +1,5 @@ +import type { Ad } from 'react-native-theoplayer'; + +export function isLinearAd(ad: Ad): boolean { + return ad.type === 'linear'; +} diff --git a/src/ui/utils/ArrayUtils.ts b/src/ui/utils/ArrayUtils.ts index 78dae8e..beb18c6 100644 --- a/src/ui/utils/ArrayUtils.ts +++ b/src/ui/utils/ArrayUtils.ts @@ -10,3 +10,15 @@ export function arrayRemoveElement(array: T[], element: T): boolean { export function arrayRemoveAt(array: T[], index: number): void { array.splice(index, 1); } + +export const arrayFind: (array: readonly T[], predicate: (element: T, index: number, array: readonly T[]) => boolean) => T | undefined = + typeof Array.prototype.find === 'function' + ? (array, predicate) => array.find(predicate) + : (array, predicate) => { + for (let i = 0; i < array.length; i++) { + if (predicate(array[i], i, array)) { + return array[i]; + } + } + return undefined; + }; From b331d5fddacb42fe7c1e2739320691e4d56be670 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 8 Feb 2024 13:35:53 +0100 Subject: [PATCH 12/46] Keep track if ad is in progress --- .../components/uicontroller/UiContainer.tsx | 21 +++++++++++++++---- src/ui/components/util/PlayerContext.ts | 6 ++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index a702a77..6c5ddec 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -1,8 +1,8 @@ import React, { PureComponent, ReactNode } from 'react'; import { Animated, Platform, StyleProp, View, ViewStyle } from 'react-native'; import { PlayerContext } from '../util/PlayerContext'; -import type { PresentationModeChangeEvent, THEOplayer } from 'react-native-theoplayer'; -import { CastEvent, CastEventType, ErrorEvent, PlayerError, PlayerEventType, PresentationMode } from 'react-native-theoplayer'; +import type { AdEvent, PresentationModeChangeEvent, THEOplayer } from 'react-native-theoplayer'; +import { AdEventType, CastEvent, CastEventType, ErrorEvent, PlayerError, PlayerEventType, PresentationMode } from 'react-native-theoplayer'; import type { THEOplayerTheme } from '../../THEOplayerTheme'; import type { MenuConstructor, UiControls } from './UiControls'; import { ErrorDisplay } from '../message/ErrorDisplay'; @@ -123,6 +123,7 @@ interface UiContainerState { paused: boolean; casting: boolean; pip: boolean; + adInProgress: boolean; } /** @@ -146,6 +147,7 @@ export class UiContainer extends PureComponent { + const type = event.subType; + if (type === AdEventType.AD_BREAK_BEGIN) { + this.setState({ adInProgress: true }); + } else if (type === AdEventType.AD_BREAK_END) { + this.setState({ adInProgress: false }); + } + }; + get buttonsEnabled_(): boolean { return this.state.buttonsEnabled; } @@ -331,7 +344,7 @@ export class UiContainer extends PureComponent; @@ -344,7 +357,7 @@ export class UiContainer extends PureComponent + {/* The View behind the UI, that is always visible.*/} {behind} {/* The Animated.View is for showing and hiding the UI*/} diff --git a/src/ui/components/util/PlayerContext.ts b/src/ui/components/util/PlayerContext.ts index acf1ad2..cce4179 100644 --- a/src/ui/components/util/PlayerContext.ts +++ b/src/ui/components/util/PlayerContext.ts @@ -17,6 +17,11 @@ export interface UiContext { * UI controls for the components to communicate with the UI. */ readonly ui: UiControls; + + /** + * Whether a linear ad is currently in progress. + */ + readonly adInProgress: boolean; } /** @@ -26,4 +31,5 @@ export const PlayerContext = React.createContext({ player: undefined as unknown as THEOplayer, style: DEFAULT_THEOPLAYER_THEME, ui: undefined as unknown as UiControls, + adInProgress: false, }); From bd6e7ffef5d63d9917a5b5792d5150f5913c2037 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 8 Feb 2024 13:39:10 +0100 Subject: [PATCH 13/46] Use context.adInProgress instead --- src/ui/components/seekbar/SeekBar.tsx | 29 ++++----------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/ui/components/seekbar/SeekBar.tsx b/src/ui/components/seekbar/SeekBar.tsx index 7d805bc..d03607e 100644 --- a/src/ui/components/seekbar/SeekBar.tsx +++ b/src/ui/components/seekbar/SeekBar.tsx @@ -1,15 +1,6 @@ import React, { PureComponent } from 'react'; import { LayoutChangeEvent, StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; -import { - AdEvent, - AdEventType, - DurationChangeEvent, - LoadedMetadataEvent, - PlayerEventType, - ProgressEvent, - TimeRange, - TimeUpdateEvent, -} from 'react-native-theoplayer'; +import { DurationChangeEvent, LoadedMetadataEvent, PlayerEventType, ProgressEvent, TimeRange, TimeUpdateEvent } from 'react-native-theoplayer'; import { PlayerContext, UiContext } from '../util/PlayerContext'; import Slider from '@react-native-community/slider'; import { SingleThumbnailView } from './thumbnail/SingleThumbnailView'; @@ -29,7 +20,6 @@ interface SeekBarState { duration: number; sliderTime: number; width: number; - playingAd: boolean; } /** @@ -44,7 +34,6 @@ export class SeekBar extends PureComponent { seekable: [], pausedDueToScrubbing: false, width: 0, - playingAd: false, }; private _seekBlockingTimeout: NodeJS.Timeout | undefined; @@ -61,7 +50,6 @@ export class SeekBar extends PureComponent { player.addEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); player.addEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); player.addEventListener(PlayerEventType.PROGRESS, this._onProgress); - player.addEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); this.setState({ ...SeekBar.initialState, sliderTime: player.currentTime, @@ -76,7 +64,6 @@ export class SeekBar extends PureComponent { player.removeEventListener(PlayerEventType.DURATION_CHANGE, this._onDurationChange); player.removeEventListener(PlayerEventType.TIME_UPDATE, this._onTimeUpdate); player.removeEventListener(PlayerEventType.PROGRESS, this._onProgress); - player.removeEventListener(PlayerEventType.AD_EVENT, this._onAdEvent); clearTimeout(this._seekBlockingTimeout); clearTimeout(this._clearIsScrubbingTimout); } @@ -90,14 +77,6 @@ export class SeekBar extends PureComponent { private _onDurationChange = (event: DurationChangeEvent) => this.setState({ duration: event.duration }); private _onProgress = (event: ProgressEvent) => this.setState({ seekable: event.seekable }); - private _onAdEvent = (event: AdEvent) => { - if (event.subType === AdEventType.AD_BREAK_BEGIN) { - this.setState({ playingAd: true }); - } else if (event.subType === AdEventType.AD_BREAK_END) { - this.setState({ playingAd: false }); - } - }; - private _onSlidingStart = (value: number) => { this.setState({ sliderTime: value }); this._prepareSeekStart(); @@ -159,7 +138,7 @@ export class SeekBar extends PureComponent { }; render() { - const { seekable, sliderTime, duration, isSeeking, width, playingAd } = this.state; + const { seekable, sliderTime, duration, isSeeking, width } = this.state; const { style } = this.props; const seekableStart = seekable.length > 0 ? seekable[0].start : 0; const seekableEnd = seekable.length > 0 ? seekable[0].end : 0; @@ -175,10 +154,10 @@ export class SeekBar extends PureComponent { )} 0) && seekable.length > 0) || playingAd} + disabled={(!(duration > 0) && seekable.length > 0) || context.adInProgress} style={[StyleSheet.absoluteFill, style]} minimumValue={seekableStart} - maximumValue={playingAd ? duration : seekableEnd} + maximumValue={context.adInProgress ? duration : seekableEnd} step={1000} onSlidingStart={this._onSlidingStart} onValueChange={this._onValueChange} From 856f4c82b856843ae5425ef1db8970930a1b75db Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Sun, 11 Feb 2024 15:10:29 +0100 Subject: [PATCH 14/46] Add AdClickThroughButton --- .../components/ads/AdClickThroughButton.tsx | 84 +++++++++++++++++++ src/ui/components/ads/barrel.ts | 1 + 2 files changed, 85 insertions(+) create mode 100644 src/ui/components/ads/AdClickThroughButton.tsx diff --git a/src/ui/components/ads/AdClickThroughButton.tsx b/src/ui/components/ads/AdClickThroughButton.tsx new file mode 100644 index 0000000..07acf48 --- /dev/null +++ b/src/ui/components/ads/AdClickThroughButton.tsx @@ -0,0 +1,84 @@ +import React, { PureComponent } from 'react'; +import { Button, Linking } from 'react-native'; +import { PlayerContext, UiContext } from '../util/PlayerContext'; +import { Ad, AdEvent, AdEventType, PlayerEventType, TimeUpdateEvent } from 'react-native-theoplayer'; +import { arrayFind } from '../../utils/ArrayUtils'; +import { isLinearAd } from '../../utils/AdUtils'; + +interface AdClickThroughButtonState { + currentAd: Ad | undefined; + clickThrough: string | undefined; +} + +export class AdClickThroughButton extends PureComponent { + private static initialState = { + currentAd: undefined, + clickThrough: undefined, + }; + + constructor(props: unknown) { + super(props); + this.state = AdClickThroughButton.initialState; + } + + componentDidMount() { + const player = (this.context as UiContext).player; + player.addEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + componentWillUnmount() { + const player = (this.context as UiContext).player; + player.removeEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + } + + private onAdEvent = (event: AdEvent) => { + const player = (this.context as UiContext).player; + if (event.subType === AdEventType.AD_BREAK_BEGIN) { + void this.update(); + player.addEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } else if (event.subType === AdEventType.AD_BREAK_END) { + this.setState(AdClickThroughButton.initialState); + player.removeEventListener(PlayerEventType.TIME_UPDATE, this.onTimeUpdateEvent); + } + }; + + private onTimeUpdateEvent = (_event: TimeUpdateEvent) => { + void this.update(); + }; + + private async update(): Promise { + const player = (this.context as UiContext).player; + const currentAds = await player.ads.currentAds(); + const linearAd = arrayFind(currentAds ?? [], isLinearAd); + const clickThrough = linearAd?.clickThrough; + if (clickThrough !== undefined) { + this.setState({ clickThrough: clickThrough }); + } + } + + private onPress = () => { + const { clickThrough } = this.state; + const player = (this.context as UiContext).player; + player.pause(); + if (clickThrough) { + void Linking.openURL(clickThrough); + } + }; + + render() { + const { clickThrough, currentAd } = this.state; + if (clickThrough === undefined) { + return <>; + } + + if (currentAd && currentAd.integration === 'google-ima') { + return <>; + } + + return ( + {(_context: UiContext) => } + ); + } +} + +AdClickThroughButton.contextType = PlayerContext; diff --git a/src/ui/components/ads/barrel.ts b/src/ui/components/ads/barrel.ts index ad5e43e..e37b6e0 100644 --- a/src/ui/components/ads/barrel.ts +++ b/src/ui/components/ads/barrel.ts @@ -1,3 +1,4 @@ +export * from './AdClickThroughButton'; export * from './AdCountdown'; export * from './AdDisplay'; export * from './AdSkipButton'; From 25ba305f9b58568dfde9127cea8502302b1cbd94 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Sun, 11 Feb 2024 16:29:20 +0100 Subject: [PATCH 15/46] Properly handle ad components after opening a menu --- src/ui/components/ads/AdClickThroughButton.tsx | 7 ++++++- src/ui/components/ads/AdCountdown.tsx | 7 ++++++- src/ui/components/ads/AdSkipButton.tsx | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/ui/components/ads/AdClickThroughButton.tsx b/src/ui/components/ads/AdClickThroughButton.tsx index 07acf48..c06f1ac 100644 --- a/src/ui/components/ads/AdClickThroughButton.tsx +++ b/src/ui/components/ads/AdClickThroughButton.tsx @@ -22,8 +22,13 @@ export class AdClickThroughButton extends PureComponent Date: Mon, 12 Feb 2024 09:35:10 +0100 Subject: [PATCH 16/46] Optimize import --- src/ui/THEOplayerDefaultUi.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/THEOplayerDefaultUi.tsx b/src/ui/THEOplayerDefaultUi.tsx index e1e4de3..27411c7 100644 --- a/src/ui/THEOplayerDefaultUi.tsx +++ b/src/ui/THEOplayerDefaultUi.tsx @@ -10,13 +10,12 @@ import { MuteButton } from './components/button/MuteButton'; import { CastMessage } from './components/message/CastMessage'; import { DEFAULT_THEOPLAYER_THEME, THEOplayerTheme } from './THEOplayerTheme'; import { Platform, StyleProp, View, ViewStyle } from 'react-native'; -import { UiContainer } from './components/uicontroller/UiContainer'; +import { FULLSCREEN_CENTER_STYLE, UiContainer } from './components/uicontroller/UiContainer'; import { PlayButton } from './components/button/PlayButton'; import { SkipButton } from './components/button/SkipButton'; import { Spacer } from './components/controlbar/Spacer'; import { ChromecastButton } from './components/button/ChromecastButton'; import { CenteredDelayedActivityIndicator } from './components/activityindicator/CenteredDelayedActivityIndicator'; -import { FULLSCREEN_CENTER_STYLE } from './components/uicontroller/UiContainer'; export interface THEOplayerDefaultUiProps { /** From a21071551f1de3c76670f8e62492386232404f80 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 12 Feb 2024 13:29:59 +0100 Subject: [PATCH 17/46] Update theoplayer version to 6.9 API changes were made that are necessary for the ad UI to work properly --- example/package-lock.json | 10 +++++----- example/package.json | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index 9807f84..78f5e01 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -40,6 +40,7 @@ "node-polyfill-webpack-plugin": "^2.0.1", "prettier": "^2.4.1", "react-test-renderer": "18.2.0", + "theoplayer": "^6.9.0", "typescript": "4.8.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", @@ -16092,11 +16093,10 @@ "dev": true }, "node_modules/theoplayer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-6.5.0.tgz", - "integrity": "sha512-7GtkSj2Z/IwtyuvuN6ot76veIAK0zEmR6nnrgnJI5OrvG4uNbI6dgr4iip3ySKC0lUsMSnospmQAXzEeeIi0SA==", - "optional": true, - "peer": true + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-6.9.0.tgz", + "integrity": "sha512-6yS7zcTVw1n2p4AaVt+KKKubvl3L3hzVUgFxKXDuVTfInuqJId2++9sIWcWE8KX1n331D9JZw7mdeejrheMbYw==", + "devOptional": true }, "node_modules/throat": { "version": "5.0.0", diff --git a/example/package.json b/example/package.json index 451b6e1..75cc2a3 100644 --- a/example/package.json +++ b/example/package.json @@ -43,6 +43,7 @@ "node-polyfill-webpack-plugin": "^2.0.1", "prettier": "^2.4.1", "react-test-renderer": "18.2.0", + "theoplayer": "^6.9.0", "typescript": "4.8.4", "webpack": "^5.89.0", "webpack-cli": "^5.1.4", diff --git a/package-lock.json b/package-lock.json index 486b409..0ed8b60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-native-svg": "^13.8.0", "react-native-theoplayer": "^2.7.0", "release-it": "^16.2.0", - "theoplayer": "^6.5.0", + "theoplayer": "^6.9.0", "typescript": "^4.1.3" }, "peerDependencies": { @@ -17134,9 +17134,9 @@ "dev": true }, "node_modules/theoplayer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-6.5.0.tgz", - "integrity": "sha512-7GtkSj2Z/IwtyuvuN6ot76veIAK0zEmR6nnrgnJI5OrvG4uNbI6dgr4iip3ySKC0lUsMSnospmQAXzEeeIi0SA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/theoplayer/-/theoplayer-6.9.0.tgz", + "integrity": "sha512-6yS7zcTVw1n2p4AaVt+KKKubvl3L3hzVUgFxKXDuVTfInuqJId2++9sIWcWE8KX1n331D9JZw7mdeejrheMbYw==", "dev": true }, "node_modules/throat": { diff --git a/package.json b/package.json index ae9414a..28eefbf 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "react-native-svg": "^13.8.0", "react-native-theoplayer": "^2.7.0", "release-it": "^16.2.0", - "theoplayer": "^6.5.0", + "theoplayer": "^6.9.0", "typescript": "^4.1.3" }, "peerDependencies": { From f9bf89500aee79ad081c06170fbb83e72fb941f8 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 12 Feb 2024 13:33:48 +0100 Subject: [PATCH 18/46] Hide theoplayer ad UI on web example page --- example/web/public/style.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/example/web/public/style.css b/example/web/public/style.css index de5abf0..d225ec7 100644 --- a/example/web/public/style.css +++ b/example/web/public/style.css @@ -15,3 +15,23 @@ body { .theoplayer-container { z-index: 0; } + +/* + * Hack: THEOplayer's own linear ad UI is not positioned correctly by default + * when using the chromeless player. + */ +.theoplayer-ad-linear { + position: absolute; + inset: 0; + z-index: 10; +} + +/* + * Hack: hide THEOplayer's built-in ad UI, so we can use our own components instead + * such as and . + */ +.theo-ad-remaining-container, +.theoplayer-ad-skip, +.theoplayer-ad-touch-clickthrough { + display: none !important; +} From 4495ea787313e5f6146f3afe851da135c03c11c6 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 14 Feb 2024 14:08:49 +0100 Subject: [PATCH 19/46] Temporary hack to fix screen shake with google ima --- example/web/public/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/example/web/public/style.css b/example/web/public/style.css index d225ec7..20b68a1 100644 --- a/example/web/public/style.css +++ b/example/web/public/style.css @@ -35,3 +35,10 @@ body { .theoplayer-ad-touch-clickthrough { display: none !important; } + +/* + * Hack: hide overflow so that when google ima is enabled the screen won't shake furiously anymore. + */ +.theoplayer-container { + overflow-y: hidden; +} From 1c8a6519d43e3a3fcb9f5a84eec7b75d57944d7c Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 12 Feb 2024 13:37:13 +0100 Subject: [PATCH 20/46] Improve AdDisplay --- src/ui/components/ads/AdDisplay.tsx | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/ui/components/ads/AdDisplay.tsx b/src/ui/components/ads/AdDisplay.tsx index ed8f7f3..1452bfb 100644 --- a/src/ui/components/ads/AdDisplay.tsx +++ b/src/ui/components/ads/AdDisplay.tsx @@ -2,6 +2,8 @@ import React, { PureComponent } from 'react'; import { PlayerContext, UiContext } from '../util/PlayerContext'; import { StyleProp, Text, TextStyle } from 'react-native'; import { AdEvent, AdEventType, PlayerEventType } from 'react-native-theoplayer'; +import { arrayFind } from '../../utils/ArrayUtils'; +import { isLinearAd } from '../../utils/AdUtils'; interface AdDisplayProps { /** @@ -29,8 +31,12 @@ export class AdDisplay extends PureComponent { } componentDidMount() { - const player = (this.context as UiContext).player; + const context = this.context as UiContext; + const player = context.player; player.addEventListener(PlayerEventType.AD_EVENT, this.onAdEvent); + if (context.adInProgress) { + void this.updateAdDisplayState(); + } } componentWillUnmount() { @@ -42,21 +48,25 @@ export class AdDisplay extends PureComponent { void this.updateAdDisplayState(event.subType); }; - private async updateAdDisplayState(type: AdEventType) { - const { adPlaying, currentAd } = this.state; - - if (type === AdEventType.AD_BREAK_BEGIN && !adPlaying) { - const player = (this.context as UiContext).player; + private async updateAdDisplayState(type?: AdEventType): Promise { + const { adPlaying } = this.state; + const context = this.context as UiContext; + if (type === AdEventType.AD_BREAK_END) { + this.setState(AdDisplay.initialState); + } else if (adPlaying || context.adInProgress || type === AdEventType.AD_BREAK_BEGIN) { + const player = context.player; const currentAdBreak = await player.ads.currentAdBreak(); - const currentAds = currentAdBreak.ads; - if (currentAds) { - this.setState({ adPlaying: true, currentAd: 0, totalAds: currentAds.length }); + const currentLinearAds = (currentAdBreak.ads ?? []).filter(isLinearAd); + let index = 0; + if (currentLinearAds.length > 1) { + const currentAds = await player.ads.currentAds(); + const currentLinearAd = arrayFind(currentAds, isLinearAd); + if (currentLinearAd) { + index = currentLinearAds.indexOf(currentLinearAd); + } } - } else if (type === AdEventType.AD_BREAK_END) { - this.setState(AdDisplay.initialState); - } else if (type === AdEventType.AD_BEGIN) { - const newCurrentAd = currentAd ? currentAd + 1 : 1; - this.setState({ currentAd: newCurrentAd }); + this.setState({ adPlaying: true, currentAd: index + 1, totalAds: currentLinearAds.length }); + return; } } From 461b5bb892e649a0e17cf3aada78c217543c1d12 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 13 Feb 2024 11:22:39 +0100 Subject: [PATCH 21/46] Fix typos --- src/ui/components/uicontroller/UiContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index 6c5ddec..c151179 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -29,7 +29,7 @@ interface UiContainerProps { */ centerStyle?: StyleProp; /** - * The style of the button slot. + * The style of the bottom slot. */ bottomStyle?: StyleProp; /** @@ -64,7 +64,7 @@ export const FULLSCREEN_CENTER_STYLE: ViewStyle = { }; /** - * The default style for the center container. + * The default style for the UI container. */ export const UI_CONTAINER_STYLE: ViewStyle = { position: 'absolute', From ef74a8df2a5520389b37252a277c5cc78ab980e1 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 13 Feb 2024 11:23:22 +0100 Subject: [PATCH 22/46] Add ad slot to UIContainer --- .../components/uicontroller/UiContainer.tsx | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index c151179..c2ee380 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -32,6 +32,10 @@ interface UiContainerProps { * The style of the bottom slot. */ bottomStyle?: StyleProp; + /** + * The style of the ad slot. + */ + adStyle?: StyleProp; /** * The components to be put in the top slot. */ @@ -44,6 +48,10 @@ interface UiContainerProps { * The components to be put in the bottom slot. */ bottom?: ReactNode; + /** + * The components to be put in the ad slot. + */ + ad?: ReactNode; /** * A slot to put components behind the UI background. */ @@ -113,6 +121,20 @@ export const BOTTOM_UI_CONTAINER_STYLE: ViewStyle = { paddingRight: 10, }; +/** + * The default style for the ad container. + */ +export const AD_UI_CONTAINER_STYLE: ViewStyle = { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + zIndex: 1, + paddingBottom: 10, + paddingLeft: 10, + paddingRight: 10, +}; + interface UiContainerState { fadeAnimation: Animated.Value; currentMenu: ReactNode | undefined; @@ -343,7 +365,7 @@ export class UiContainer extends PureComponent {firstPlay && {top}} {center} - {firstPlay && {bottom}} + {!adInProgress && firstPlay && {bottom}} + {adInProgress && {ad}} {children} )} From c18931d1286d772937c6965551b5757d78a3f6d1 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 13 Feb 2024 11:23:47 +0100 Subject: [PATCH 23/46] Update Seekbar styling --- src/ui/components/seekbar/SeekBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/seekbar/SeekBar.tsx b/src/ui/components/seekbar/SeekBar.tsx index d03607e..24a693f 100644 --- a/src/ui/components/seekbar/SeekBar.tsx +++ b/src/ui/components/seekbar/SeekBar.tsx @@ -146,7 +146,7 @@ export class SeekBar extends PureComponent { {(context: UiContext) => ( { this.setState({ width: event.nativeEvent.layout.width }); }}> From f1211b3f27c83431141a85bda8ef5c0b5dd7dfed Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 13 Feb 2024 11:26:19 +0100 Subject: [PATCH 24/46] Update THEOplayerDefaultUi with ad slot --- src/ui/THEOplayerDefaultUi.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ui/THEOplayerDefaultUi.tsx b/src/ui/THEOplayerDefaultUi.tsx index 27411c7..9af1bce 100644 --- a/src/ui/THEOplayerDefaultUi.tsx +++ b/src/ui/THEOplayerDefaultUi.tsx @@ -100,6 +100,14 @@ export function THEOplayerDefaultUi(props: THEOplayerDefaultUiProps) { } + ad={ + <> + + + + + + } /> )} From 5a9ab1f2ee63accf848686e1fb15bada3020704c Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 16 Feb 2024 16:42:01 +0100 Subject: [PATCH 25/46] Update example react-native-theoplayer --- example/package-lock.json | 8 ++++---- example/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index 78f5e01..8e73654 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -14,7 +14,7 @@ "react-native": "0.72.7", "react-native-google-cast": "^4.6.2", "react-native-svg": "^14.0.0", - "react-native-theoplayer": "^3.1.0", + "react-native-theoplayer": "^3.7.1", "react-native-web": "^0.19.9", "react-native-web-image-loader": "^0.1.1" }, @@ -14516,9 +14516,9 @@ } }, "node_modules/react-native-theoplayer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-3.1.0.tgz", - "integrity": "sha512-eNGrDD4i15m3vbN0p7t0XK0z/u6dfDWHry4OHGDicaEtvJ944ZlEDc1biVnITx00SscQejg7D1R2yTWOYHf8Mg==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-3.7.1.tgz", + "integrity": "sha512-gnU6mk2fkAtzlGj41j28u9HgTHItZEOrrBPZfvuXVh6hvTQvyqmL3g/7rmOQTcRbLpLugBfQT7Do6qqaKVwvRg==", "dependencies": { "buffer": "^6.0.3" }, diff --git a/example/package.json b/example/package.json index 75cc2a3..73c260a 100644 --- a/example/package.json +++ b/example/package.json @@ -17,7 +17,7 @@ "react-native": "0.72.7", "react-native-google-cast": "^4.6.2", "react-native-svg": "^14.0.0", - "react-native-theoplayer": "^3.1.0", + "react-native-theoplayer": "^3.7.1", "react-native-web": "^0.19.9", "react-native-web-image-loader": "^0.1.1" }, From a4734940d8aa391457101c14913091648a36d560 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 14 Feb 2024 14:55:27 +0100 Subject: [PATCH 26/46] Add initial mobile ad specific layout --- .../components/uicontroller/UiContainer.tsx | 89 +++++++++++++------ 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index c2ee380..d7e558b 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -1,5 +1,5 @@ import React, { PureComponent, ReactNode } from 'react'; -import { Animated, Platform, StyleProp, View, ViewStyle } from 'react-native'; +import { Animated, Platform, StyleProp, TouchableOpacity, View, ViewStyle } from 'react-native'; import { PlayerContext } from '../util/PlayerContext'; import type { AdEvent, PresentationModeChangeEvent, THEOplayer } from 'react-native-theoplayer'; import { AdEventType, CastEvent, CastEventType, ErrorEvent, PlayerError, PlayerEventType, PresentationMode } from 'react-native-theoplayer'; @@ -85,6 +85,20 @@ export const UI_CONTAINER_STYLE: ViewStyle = { overflow: 'hidden', }; +/** + * The default style for the ad container. + */ +export const AD_CONTAINER_STYLE: ViewStyle = { + position: 'absolute', + top: 100, + left: 0, + bottom: 100, + right: 0, + zIndex: 0, + justifyContent: 'center', + overflow: 'hidden', +}; + /** * The default style for the top container. */ @@ -364,6 +378,14 @@ export class UiContainer extends PureComponent { + if (this.state.paused) { + this.props.player.play(); + } else { + this.props.player.pause(); + } + }; + render() { const { player, theme, top, center, bottom, ad, children, style, topStyle, centerStyle, bottomStyle, adStyle, behind } = this.props; const { fadeAnimation, currentMenu, error, firstPlay, pip, showing, adInProgress } = this.state; @@ -376,37 +398,50 @@ export class UiContainer extends PureComponent; } - const combinedContainerStyle = [UI_CONTAINER_STYLE, style]; + const combinedUiContainerStyle = [UI_CONTAINER_STYLE, style]; + const combinedAdContainerStyle = [AD_CONTAINER_STYLE, style]; + + const showMobileAdLayout = adInProgress && Platform.OS != 'web'; return ( {/* The View behind the UI, that is always visible.*/} - {behind} + + {behind} + {/* The Animated.View is for showing and hiding the UI*/} - - <> - {/* The UI background */} - - - {/* The Settings Menu */} - {currentMenu !== undefined && {currentMenu}} - - {/* The UI control bars*/} - {currentMenu === undefined && ( - <> - {firstPlay && {top}} - {center} - {!adInProgress && firstPlay && {bottom}} - {adInProgress && {ad}} - {children} - - )} - - + {!showMobileAdLayout && ( + + <> + {/* The UI background */} + + + {/* The Settings Menu */} + {currentMenu !== undefined && {currentMenu}} + + {/* The UI control bars*/} + {currentMenu === undefined && ( + <> + {firstPlay && {top}} + {center} + {!adInProgress && firstPlay && {bottom}} + {adInProgress && {ad}} + {children} + + )} + + + )} + {/* Simplistic ad view to allow play pause during an ad on mobile. */} + {showMobileAdLayout && ( + + + + )} ); } From 59814c054d16b988259a890182bc82a2ad060818 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 19 Feb 2024 12:59:01 +0100 Subject: [PATCH 27/46] Fix seekbar with DAI --- src/ui/components/seekbar/SeekBar.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/components/seekbar/SeekBar.tsx b/src/ui/components/seekbar/SeekBar.tsx index 24a693f..ab9a433 100644 --- a/src/ui/components/seekbar/SeekBar.tsx +++ b/src/ui/components/seekbar/SeekBar.tsx @@ -140,8 +140,9 @@ export class SeekBar extends PureComponent { render() { const { seekable, sliderTime, duration, isSeeking, width } = this.state; const { style } = this.props; + const normalizedDuration = isNaN(duration) ? 0 : duration; const seekableStart = seekable.length > 0 ? seekable[0].start : 0; - const seekableEnd = seekable.length > 0 ? seekable[0].end : 0; + const seekableEnd = seekable.length > 0 ? seekable[0].end : normalizedDuration; return ( {(context: UiContext) => ( @@ -157,7 +158,7 @@ export class SeekBar extends PureComponent { disabled={(!(duration > 0) && seekable.length > 0) || context.adInProgress} style={[StyleSheet.absoluteFill, style]} minimumValue={seekableStart} - maximumValue={context.adInProgress ? duration : seekableEnd} + maximumValue={seekableEnd} step={1000} onSlidingStart={this._onSlidingStart} onValueChange={this._onValueChange} From 0bd9d1a1467ca512a80c62aecd3522b04cf42d6a Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 20 Feb 2024 13:29:01 +0100 Subject: [PATCH 28/46] Enable google DAI for example --- example/android/gradle.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 9ed4428..094ad1e 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -45,3 +45,4 @@ hermesEnabled=true # Enable THEOplayer Extensions (default: disabled) THEOplayer_extensionGoogleIMA = true +THEOplayer_extensionGoogleDAI = true From 406572722579f231f0b7dc93ec1c959652d5ac78 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 14 Feb 2024 16:09:55 +0100 Subject: [PATCH 29/46] Update example App --- example/App.tsx | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index 2518e2a..046c592 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -12,6 +12,7 @@ import { PlaybackRateSubMenu, PlayButton, QualitySubMenu, + SeekBar, SettingsMenuButton, SkipButton, Spacer, @@ -26,6 +27,9 @@ import { } from 'react-native-theoplayer'; import {Platform, StyleSheet, View, ViewStyle} from 'react-native'; +import {AdDisplay} from '../src/ui/components/ads/AdDisplay'; +import {AdCountdown} from '../src/ui/components/ads/AdCountdown'; +import {AdSkipButton} from '../src/ui/components/ads/AdSkipButton'; const playerConfig: PlayerConfiguration = { // Get your THEOplayer license from https://portal.theoplayer.com/ @@ -114,8 +118,9 @@ export default function App() { } bottom={ <> - - + + + @@ -125,6 +130,20 @@ export default function App() { } + ad={ + <> + + + + + + + + + + + + } /> )} From a26047e8e71ac1bf93f2cf3884bdcae374c72887 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 20 Feb 2024 13:56:48 +0100 Subject: [PATCH 30/46] Update THEOplayer Default ad UI --- src/ui/THEOplayerDefaultUi.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ui/THEOplayerDefaultUi.tsx b/src/ui/THEOplayerDefaultUi.tsx index 9af1bce..254a938 100644 --- a/src/ui/THEOplayerDefaultUi.tsx +++ b/src/ui/THEOplayerDefaultUi.tsx @@ -16,6 +16,9 @@ import { SkipButton } from './components/button/SkipButton'; import { Spacer } from './components/controlbar/Spacer'; import { ChromecastButton } from './components/button/ChromecastButton'; import { CenteredDelayedActivityIndicator } from './components/activityindicator/CenteredDelayedActivityIndicator'; +import { AdDisplay } from './components/ads/AdDisplay'; +import { AdCountdown } from './components/ads/AdCountdown'; +import { AdSkipButton } from './components/ads/AdSkipButton'; export interface THEOplayerDefaultUiProps { /** @@ -102,6 +105,12 @@ export function THEOplayerDefaultUi(props: THEOplayerDefaultUiProps) { } ad={ <> + + + + + + From 8fac848a649dd4e82044fe45a8adb2c34d3133fb Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 20 Feb 2024 14:19:56 +0100 Subject: [PATCH 31/46] Simplify --- src/ui/components/ads/AdClickThroughButton.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/components/ads/AdClickThroughButton.tsx b/src/ui/components/ads/AdClickThroughButton.tsx index c06f1ac..01681c3 100644 --- a/src/ui/components/ads/AdClickThroughButton.tsx +++ b/src/ui/components/ads/AdClickThroughButton.tsx @@ -72,11 +72,8 @@ export class AdClickThroughButton extends PureComponent; - } - if (currentAd && currentAd.integration === 'google-ima') { + if (clickThrough === undefined || (currentAd && currentAd.integration === 'google-ima')) { return <>; } From de524c2d9cc254abed6694fdfcd5d84b5f612363 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Tue, 20 Feb 2024 14:49:55 +0100 Subject: [PATCH 32/46] Simplify --- src/ui/components/ads/AdSkipButton.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ui/components/ads/AdSkipButton.tsx b/src/ui/components/ads/AdSkipButton.tsx index 0bd9b8f..3c3acd0 100644 --- a/src/ui/components/ads/AdSkipButton.tsx +++ b/src/ui/components/ads/AdSkipButton.tsx @@ -80,11 +80,8 @@ export class AdSkipButton extends PureComponent; - } - if (currentAd && currentAd.integration === 'google-ima') { + if (timeToSkip === undefined || isNaN(timeToSkip) || (currentAd && currentAd.integration === 'google-ima')) { return <>; } From 1c3223347a1d6cd4bc9375a2958dde4e13dcb6a8 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Thu, 22 Feb 2024 11:50:16 +0100 Subject: [PATCH 33/46] Simplify --- src/ui/components/uicontroller/UiContainer.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index d7e558b..a5bc19a 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -138,16 +138,7 @@ export const BOTTOM_UI_CONTAINER_STYLE: ViewStyle = { /** * The default style for the ad container. */ -export const AD_UI_CONTAINER_STYLE: ViewStyle = { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - zIndex: 1, - paddingBottom: 10, - paddingLeft: 10, - paddingRight: 10, -}; +export const AD_UI_CONTAINER_STYLE: ViewStyle = BOTTOM_UI_CONTAINER_STYLE; interface UiContainerState { fadeAnimation: Animated.Value; From fc6da42fac15c6805d50a5ff00077f1ab9fe4646 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 23 Feb 2024 11:01:55 +0100 Subject: [PATCH 34/46] Update ad skip button title --- src/ui/components/ads/AdSkipButton.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ui/components/ads/AdSkipButton.tsx b/src/ui/components/ads/AdSkipButton.tsx index 3c3acd0..d016762 100644 --- a/src/ui/components/ads/AdSkipButton.tsx +++ b/src/ui/components/ads/AdSkipButton.tsx @@ -94,9 +94,7 @@ export class AdSkipButton extends PureComponent{(_context: UiContext) => } - ); + return {(_context: UiContext) => }; } } From 6d0a63c8d5a1e3e79d4fdc9f77ad74a5a4233db3 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 23 Feb 2024 11:04:01 +0100 Subject: [PATCH 35/46] Update ad display styling --- src/ui/THEOplayerTheme.ts | 10 ++++++++++ src/ui/components/ads/AdDisplay.tsx | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/ui/THEOplayerTheme.ts b/src/ui/THEOplayerTheme.ts index 58c19b6..1cbc9c5 100644 --- a/src/ui/THEOplayerTheme.ts +++ b/src/ui/THEOplayerTheme.ts @@ -44,6 +44,14 @@ export interface ColorTheme { * The color of the dot on the seek bar. */ seekBarDot: string; + /** + * The color of the ad display text + */ + adDiplayText: string; + /** + * The color of the ad display background. + */ + adDisplayBackground: string; } /** @@ -97,6 +105,8 @@ export const DEFAULT_THEOPLAYER_THEME: THEOplayerTheme = { seekBarMinimum: '#FFFFFF', seekBarMaximum: '#FFFFFF50', seekBarDot: '#FFFFFF', + adDiplayText: '#000', + adDisplayBackground: '#FFC50F', }, text: { textAlignVertical: 'center', diff --git a/src/ui/components/ads/AdDisplay.tsx b/src/ui/components/ads/AdDisplay.tsx index 1452bfb..cad0595 100644 --- a/src/ui/components/ads/AdDisplay.tsx +++ b/src/ui/components/ads/AdDisplay.tsx @@ -81,7 +81,21 @@ export class AdDisplay extends PureComponent { return ( - {(context: UiContext) => {label}} + {(context: UiContext) => ( + + {label} + + )} ); } From f4599161a710bc27357bd257b3f28805a995cfe7 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 23 Feb 2024 11:51:38 +0100 Subject: [PATCH 36/46] Update theoplayer version --- example/package-lock.json | 8 ++++---- example/package.json | 2 +- package-lock.json | 17 ++++++----------- package.json | 3 ++- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/example/package-lock.json b/example/package-lock.json index 8e73654..fa7949b 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -14,7 +14,7 @@ "react-native": "0.72.7", "react-native-google-cast": "^4.6.2", "react-native-svg": "^14.0.0", - "react-native-theoplayer": "^3.7.1", + "react-native-theoplayer": "^3.8.0", "react-native-web": "^0.19.9", "react-native-web-image-loader": "^0.1.1" }, @@ -14516,9 +14516,9 @@ } }, "node_modules/react-native-theoplayer": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-3.7.1.tgz", - "integrity": "sha512-gnU6mk2fkAtzlGj41j28u9HgTHItZEOrrBPZfvuXVh6hvTQvyqmL3g/7rmOQTcRbLpLugBfQT7Do6qqaKVwvRg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-3.8.0.tgz", + "integrity": "sha512-/1ojzfuiu+Ul72oRbUIK37AKTa7npE24/HDq23pqExL0ByBfHCVT70jD2xSJ+oaw+fpYheloPzAq/TZGaMIvRg==", "dependencies": { "buffer": "^6.0.3" }, diff --git a/example/package.json b/example/package.json index 73c260a..e4bb9c4 100644 --- a/example/package.json +++ b/example/package.json @@ -17,7 +17,7 @@ "react-native": "0.72.7", "react-native-google-cast": "^4.6.2", "react-native-svg": "^14.0.0", - "react-native-theoplayer": "^3.7.1", + "react-native-theoplayer": "^3.8.0", "react-native-web": "^0.19.9", "react-native-web-image-loader": "^0.1.1" }, diff --git a/package-lock.json b/package-lock.json index 0ed8b60..a41e2ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "react-native-builder-bob": "^0.18.0", "react-native-google-cast": "^4.6.2", "react-native-svg": "^13.8.0", - "react-native-theoplayer": "^2.7.0", + "react-native-theoplayer": "^3.8.0", "release-it": "^16.2.0", "theoplayer": "^6.9.0", "typescript": "^4.1.3" @@ -45,6 +45,7 @@ "react-native": "*", "react-native-google-cast": "^4.6.2", "react-native-svg": "^13.8.0", + "react-native-theoplayer": ">=2.7.0", "theoplayer": ">=5.0.1" }, "peerDependenciesMeta": { @@ -14702,18 +14703,12 @@ } }, "node_modules/react-native-theoplayer": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-2.7.0.tgz", - "integrity": "sha512-wEFC69uk2rWfbAMfi8KHQa/SN/KuLdY0yJiCOszyR9oDw5hdDoepYkv+06ro9POYfsbl2jIb7ZAbUrFD+QCEkQ==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/react-native-theoplayer/-/react-native-theoplayer-3.8.0.tgz", + "integrity": "sha512-/1ojzfuiu+Ul72oRbUIK37AKTa7npE24/HDq23pqExL0ByBfHCVT70jD2xSJ+oaw+fpYheloPzAq/TZGaMIvRg==", "dev": true, "dependencies": { - "@react-native-community/slider": "^4.4.2", - "buffer": "^6.0.3", - "react-native-google-cast": "^4.6.2", - "react-native-svg": "^13.8.0", - "react-native-svg-web": "^1.0.0", - "react-native-url-polyfill": "^1.3.0", - "url-polyfill": "^1.1.12" + "buffer": "^6.0.3" }, "peerDependencies": { "react": "*", diff --git a/package.json b/package.json index 28eefbf..40050d8 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "react-native-builder-bob": "^0.18.0", "react-native-google-cast": "^4.6.2", "react-native-svg": "^13.8.0", - "react-native-theoplayer": "^2.7.0", + "react-native-theoplayer": "^3.8.0", "release-it": "^16.2.0", "theoplayer": "^6.9.0", "typescript": "^4.1.3" @@ -68,6 +68,7 @@ "react-native": "*", "react-native-google-cast": "^4.6.2", "react-native-svg": "^13.8.0", + "react-native-theoplayer": ">=2.7.0", "theoplayer": ">=5.0.1" }, "peerDependenciesMeta": { From edfa824ed4dcd6e4e1fd604535118c0a1078406b Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 26 Feb 2024 10:52:55 +0100 Subject: [PATCH 37/46] Update AdCountDown styling --- src/ui/components/ads/AdCountdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/ads/AdCountdown.tsx b/src/ui/components/ads/AdCountdown.tsx index 3db0d46..6a09095 100644 --- a/src/ui/components/ads/AdCountdown.tsx +++ b/src/ui/components/ads/AdCountdown.tsx @@ -73,7 +73,7 @@ export class AdCountdown extends PureComponent - {(context: UiContext) => {label}} + {(context: UiContext) => {label}} ); } From 33dda2405d4287763a5e4461225557021cdb5043 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Fri, 23 Feb 2024 14:36:36 +0100 Subject: [PATCH 38/46] Remove custom app styling --- example/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index 046c592..7a11b02 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -133,8 +133,8 @@ export default function App() { ad={ <> - - + + From 4d13824c6ccc0f8573985a02055c5df2665aa6bf Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Sun, 25 Feb 2024 15:07:14 +0100 Subject: [PATCH 39/46] Add skip next svg --- src/ui/components/button/svg/SkipNext.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/ui/components/button/svg/SkipNext.tsx diff --git a/src/ui/components/button/svg/SkipNext.tsx b/src/ui/components/button/svg/SkipNext.tsx new file mode 100644 index 0000000..90761dc --- /dev/null +++ b/src/ui/components/button/svg/SkipNext.tsx @@ -0,0 +1,18 @@ +import type { SvgProps } from 'react-native-svg'; +import Svg, { Path } from 'react-native-svg'; +import React from 'react'; +import { SvgContext } from './SvgUtils'; + +export const SkipNextSvg = (props: SvgProps) => { + return ( + + {(context) => ( + <> + + + + + )} + + ); +}; From 4bf745b4a6eb07af1b1e201ea7aea20b01093954 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Sun, 25 Feb 2024 19:55:30 +0100 Subject: [PATCH 40/46] Update ad click through styling --- .../components/ads/AdClickThroughButton.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/ui/components/ads/AdClickThroughButton.tsx b/src/ui/components/ads/AdClickThroughButton.tsx index 01681c3..a336566 100644 --- a/src/ui/components/ads/AdClickThroughButton.tsx +++ b/src/ui/components/ads/AdClickThroughButton.tsx @@ -1,22 +1,29 @@ import React, { PureComponent } from 'react'; -import { Button, Linking } from 'react-native'; +import { Linking, StyleProp, Text, TextStyle, TouchableOpacity } from 'react-native'; import { PlayerContext, UiContext } from '../util/PlayerContext'; import { Ad, AdEvent, AdEventType, PlayerEventType, TimeUpdateEvent } from 'react-native-theoplayer'; import { arrayFind } from '../../utils/ArrayUtils'; import { isLinearAd } from '../../utils/AdUtils'; +interface AdClickThroughButtonProps { + /** + * Optional style applied to the ad click through button + */ + style?: StyleProp; +} + interface AdClickThroughButtonState { currentAd: Ad | undefined; clickThrough: string | undefined; } -export class AdClickThroughButton extends PureComponent { +export class AdClickThroughButton extends PureComponent { private static initialState = { currentAd: undefined, clickThrough: undefined, }; - constructor(props: unknown) { + constructor(props: AdClickThroughButtonProps) { super(props); this.state = AdClickThroughButton.initialState; } @@ -72,13 +79,20 @@ export class AdClickThroughButton extends PureComponent; } return ( - {(_context: UiContext) => } + + {(context: UiContext) => ( + + Visit Advertiser + + )} + ); } } From 6e49f1d9fcfb637e06e70de9b126f10ca60825cd Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Sun, 25 Feb 2024 20:07:37 +0100 Subject: [PATCH 41/46] Update AdSkipButton --- src/ui/THEOplayerTheme.ts | 5 ++++ src/ui/components/ads/AdSkipButton.tsx | 32 ++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/ui/THEOplayerTheme.ts b/src/ui/THEOplayerTheme.ts index 1cbc9c5..4c9119a 100644 --- a/src/ui/THEOplayerTheme.ts +++ b/src/ui/THEOplayerTheme.ts @@ -52,6 +52,10 @@ export interface ColorTheme { * The color of the ad display background. */ adDisplayBackground: string; + /** + * The color of the ad skip button background. + */ + adSkipBackground: string; } /** @@ -107,6 +111,7 @@ export const DEFAULT_THEOPLAYER_THEME: THEOplayerTheme = { seekBarDot: '#FFFFFF', adDiplayText: '#000', adDisplayBackground: '#FFC50F', + adSkipBackground: '#00000070', }, text: { textAlignVertical: 'center', diff --git a/src/ui/components/ads/AdSkipButton.tsx b/src/ui/components/ads/AdSkipButton.tsx index d016762..a73bd69 100644 --- a/src/ui/components/ads/AdSkipButton.tsx +++ b/src/ui/components/ads/AdSkipButton.tsx @@ -1,15 +1,25 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, ReactNode } from 'react'; import { PlayerContext, UiContext } from '../util/PlayerContext'; -import { Button, StyleProp, Text, TextStyle } from 'react-native'; +import { StyleProp, Text, TextStyle, TouchableOpacity, View } from 'react-native'; import { Ad, AdEvent, AdEventType, PlayerEventType, TimeUpdateEvent } from 'react-native-theoplayer'; import { arrayFind } from '../../utils/ArrayUtils'; import { isLinearAd } from '../../utils/AdUtils'; +import { ActionButton } from '../button/actionbutton/ActionButton'; +import { SkipNextSvg } from '../button/svg/SkipNext'; interface AdSkipButtonProps { /** - * Optional style applied to the AdSkipButton + * Optional style applied to the ad skip button */ style?: StyleProp; + /** + * The style overrides for the text in the ad skip button. + */ + textStyle?: StyleProp; + /** + * The icon components used in the button. + */ + icon?: ReactNode; } interface AdSkipButtonState { @@ -79,7 +89,7 @@ export class AdSkipButton extends PureComponent; @@ -94,7 +104,19 @@ export class AdSkipButton extends PureComponent{(_context: UiContext) => }; + const skipSvg: ReactNode = icon ?? ; + return ( + + {(context: UiContext) => ( + + + Skip Ad + + + + )} + + ); } } From 2e7f0f55d57501912f9227b3cba91975e0b70e0d Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 26 Feb 2024 13:12:36 +0100 Subject: [PATCH 42/46] Expand ad container --- example/App.tsx | 43 ++++++++---- src/ui/THEOplayerDefaultUi.tsx | 43 ++++++++---- .../components/uicontroller/UiContainer.tsx | 70 ++++++++++++++++--- 3 files changed, 119 insertions(+), 37 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index 7a11b02..d046105 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import {useState} from 'react'; import { + AdClickThroughButton, CenteredControlBar, CenteredDelayedActivityIndicator, ControlBar, @@ -130,20 +131,34 @@ export default function App() { } - ad={ - <> - - - - - - - - - - - - } + ad={{ + top: ( + <> + + + + + ), + center: ( + <> + } /> + + ), + bottom: ( + <> + + + + + + + + + + + + ), + }} /> )} diff --git a/src/ui/THEOplayerDefaultUi.tsx b/src/ui/THEOplayerDefaultUi.tsx index 254a938..3226b0d 100644 --- a/src/ui/THEOplayerDefaultUi.tsx +++ b/src/ui/THEOplayerDefaultUi.tsx @@ -19,6 +19,7 @@ import { CenteredDelayedActivityIndicator } from './components/activityindicator import { AdDisplay } from './components/ads/AdDisplay'; import { AdCountdown } from './components/ads/AdCountdown'; import { AdSkipButton } from './components/ads/AdSkipButton'; +import { AdClickThroughButton } from './components/ads/AdClickThroughButton'; export interface THEOplayerDefaultUiProps { /** @@ -103,20 +104,34 @@ export function THEOplayerDefaultUi(props: THEOplayerDefaultUiProps) { } - ad={ - <> - - - - - - - - - - - - } + ad={{ + top: ( + <> + + + + + ), + center: ( + <> + } /> + + ), + bottom: ( + <> + + + + + + + + + + + + ), + }} /> )} diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index a5bc19a..4ed204a 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -33,9 +33,17 @@ interface UiContainerProps { */ bottomStyle?: StyleProp; /** - * The style of the ad slot. + * The style of the top ad slot. */ - adStyle?: StyleProp; + adTopStyle?: StyleProp; + /** + * The style of the center ad slot. + */ + adCenterStyle?: StyleProp; + /** + * The style of the bottom ad slot. + */ + adBottomStyle?: StyleProp; /** * The components to be put in the top slot. */ @@ -49,15 +57,33 @@ interface UiContainerProps { */ bottom?: ReactNode; /** - * The components to be put in the ad slot. + * The components to be put in the ad slots. + * + * @remarks + *
- Currently only supported for web. */ - ad?: ReactNode; + ad?: AdUiContainer; /** * A slot to put components behind the UI background. */ behind?: ReactNode; } +interface AdUiContainer { + /** + * The components to be put in the top slot during an ad. + */ + top?: ReactNode; + /** + * The components to be put in the center slot during an ad. + */ + center?: ReactNode; + /** + * The components to be put in the bottom slot during an ad. + */ + bottom?: ReactNode; +} + /** * The default style for a fullscreen centered view. */ @@ -138,7 +164,9 @@ export const BOTTOM_UI_CONTAINER_STYLE: ViewStyle = { /** * The default style for the ad container. */ -export const AD_UI_CONTAINER_STYLE: ViewStyle = BOTTOM_UI_CONTAINER_STYLE; +export const AD_UI_TOP_CONTAINER_STYLE: ViewStyle = TOP_UI_CONTAINER_STYLE; +export const AD_UI_CENTER_CONTAINER_STYLE: ViewStyle = CENTER_UI_CONTAINER_STYLE; +export const AD_UI_BOTTOM_CONTAINER_STYLE: ViewStyle = BOTTOM_UI_CONTAINER_STYLE; interface UiContainerState { fadeAnimation: Animated.Value; @@ -378,7 +406,23 @@ export class UiContainer extends PureComponent{currentMenu}} {/* The UI control bars*/} - {currentMenu === undefined && ( + {currentMenu === undefined && !adInProgress && ( <> {firstPlay && {top}} {center} - {!adInProgress && firstPlay && {bottom}} - {adInProgress && {ad}} + {firstPlay && {bottom}} {children} )} + + {/* The Ad UI */} + {currentMenu === undefined && adInProgress && ( + <> + {ad?.top} + {ad?.center} + {ad?.bottom} + + )} )} From 76af8d038dc2dcc9d52c1e867f02b73c089b5984 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Mon, 26 Feb 2024 16:15:44 +0100 Subject: [PATCH 43/46] Separate ad slots into own prop --- example/App.tsx | 54 +++++++++---------- src/ui/THEOplayerDefaultUi.tsx | 54 +++++++++---------- .../components/uicontroller/UiContainer.tsx | 39 +++++++------- 3 files changed, 72 insertions(+), 75 deletions(-) diff --git a/example/App.tsx b/example/App.tsx index d046105..e3263b2 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -131,34 +131,32 @@ export default function App() { } - ad={{ - top: ( - <> - - - - - ), - center: ( - <> - } /> - - ), - bottom: ( - <> - - - - - - - - - - - - ), - }} + adTop={ + <> + + + + + } + adCenter={ + <> + } /> + + } + adBottom={ + <> + + + + + + + + + + + + } /> )} diff --git a/src/ui/THEOplayerDefaultUi.tsx b/src/ui/THEOplayerDefaultUi.tsx index 3226b0d..60b0a8e 100644 --- a/src/ui/THEOplayerDefaultUi.tsx +++ b/src/ui/THEOplayerDefaultUi.tsx @@ -104,34 +104,32 @@ export function THEOplayerDefaultUi(props: THEOplayerDefaultUiProps) { } - ad={{ - top: ( - <> - - - - - ), - center: ( - <> - } /> - - ), - bottom: ( - <> - - - - - - - - - - - - ), - }} + adTop={ + <> + + + + + } + adCenter={ + <> + } /> + + } + adBottom={ + <> + + + + + + + + + + + + } /> )} diff --git a/src/ui/components/uicontroller/UiContainer.tsx b/src/ui/components/uicontroller/UiContainer.tsx index 4ed204a..6eb783e 100644 --- a/src/ui/components/uicontroller/UiContainer.tsx +++ b/src/ui/components/uicontroller/UiContainer.tsx @@ -57,31 +57,30 @@ interface UiContainerProps { */ bottom?: ReactNode; /** - * The components to be put in the ad slots. + * The components to be put in the top slot during an ad. * * @remarks *
- Currently only supported for web. */ - ad?: AdUiContainer; - /** - * A slot to put components behind the UI background. - */ - behind?: ReactNode; -} - -interface AdUiContainer { - /** - * The components to be put in the top slot during an ad. - */ - top?: ReactNode; + adTop?: ReactNode; /** * The components to be put in the center slot during an ad. + * + * @remarks + *
- Currently only supported for web. */ - center?: ReactNode; + adCenter?: ReactNode; /** * The components to be put in the bottom slot during an ad. + * + * @remarks + *
- Currently only supported for web. */ - bottom?: ReactNode; + adBottom?: ReactNode; + /** + * A slot to put components behind the UI background. + */ + behind?: ReactNode; } /** @@ -412,7 +411,9 @@ export class UiContainer extends PureComponent - {ad?.top} - {ad?.center} - {ad?.bottom} + {adTop} + {adCenter} + {adBottom} )} From 570d79c95c8fe039a98c805985c8553063d084b5 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 6 Mar 2024 10:28:10 +0100 Subject: [PATCH 44/46] Add missing changelog for CSAI support --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22d3c1a..34b0668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## Unreleased +### Added + +- Added support for CSAI. + ### Fixed - Fixed connected state for chromecastButton to not take into account the casting state in general (e.g. airplay should not influence this state). From 89c0c6db7762c379bf51b0063b2c7f5407ca8ea4 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 6 Mar 2024 10:29:30 +0100 Subject: [PATCH 45/46] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b0668..056322a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.5.0] - 24-03-06 ### Added From 332c66534c03b96d421888bb47285633a8291b39 Mon Sep 17 00:00:00 2001 From: Jeroen Veltmans Date: Wed, 6 Mar 2024 10:33:04 +0100 Subject: [PATCH 46/46] 0.5.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a41e2ef..0f715e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@theoplayer/react-native-ui", - "version": "0.4.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@theoplayer/react-native-ui", - "version": "0.4.0", + "version": "0.5.0", "license": "SEE LICENSE AT https://www.theoplayer.com/terms", "dependencies": { "buffer": "^6.0.3", diff --git a/package.json b/package.json index 40050d8..cd63523 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@theoplayer/react-native-ui", - "version": "0.4.0", + "version": "0.5.0", "description": "A React Native UI for @theoplayer/react-native", "main": "lib/commonjs/index", "module": "lib/module/index",