Skip to content

Commit

Permalink
Merge pull request #465 from THEOplayer/release/v8.11.1
Browse files Browse the repository at this point in the history
Release/v8.11.1
  • Loading branch information
tvanlaerhoven authored Dec 18, 2024
2 parents 82331d5 + 8631d28 commit 8e96427
Show file tree
Hide file tree
Showing 14 changed files with 92 additions and 97 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,14 @@ ios/custom/Frameworks/tvos/*.xcframework

# Jekyll / GitHub Pages
_site/

Gemfile.lock
vendor
.bundle
src/manifest.json

# E2E app
e2e/cavy_results.md

# Example app
example/dist
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@ 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.1.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [8.11.1] - 24-12-18

### Fixed

- Fixed the picture-in-picture presentationMode for THEOlive sources on Web.

### Changed

- Deprecated the use of the `enableTHEOlive` flag in `PlayerConfiguration` as THEOlive support is always enabled.

## [8.11.0] - 24-12-13

### Added

- Added support for THEOlive on tvOS.

## [8.10.0] - 24-12-06

## Added
### Added

- Added support for THEOlive on iOS.

Expand Down
10 changes: 2 additions & 8 deletions e2e/src/tests/Ads.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { TestScope } from 'cavy';
import { AdDescription, AdEventType, PlayerEventType, SourceDescription, AdEvent } from 'react-native-theoplayer';
import hls from '../res/hls.json';
import ads from '../res/ads.json';
import { AdEventType, PlayerEventType, AdEvent } from 'react-native-theoplayer';
import { getTestPlayer } from '../components/TestableTHEOplayerView';
import { waitForPlayerEvents, waitForPlayerEventTypes } from '../utils/Actions';
import { TestSourceDescription, TestSources } from '../utils/SourceUtils';

function extendSourceWithAds(source: SourceDescription, ad: AdDescription): SourceDescription {
return { ...source, ads: [ad] };
}

export default function (spec: TestScope) {
TestSources()
.withAds()
Expand All @@ -26,7 +20,7 @@ export default function (spec: TestScope) {

// Start autoplay
player.autoplay = true;
player.source = extendSourceWithAds(hls[0], ads[0] as AdDescription);
player.source = testSource.source;

// Expect events.
await playEventsPromise;
Expand Down
2 changes: 1 addition & 1 deletion e2e/src/tests/Connector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function (spec: TestScope) {
{},
{
customerKey: 'testCustomerKey',
gatewayUrl: 'testGgatewayUrl',
gatewayUrl: 'testGatewayUrl',
},
);
},
Expand Down
2 changes: 1 addition & 1 deletion e2e/src/tests/PresentationMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function (spec: TestScope) {
const inlinePromise = waitForPlayerEvent(player, {
type: PlayerEventType.PRESENTATIONMODE_CHANGE,
presentationMode: PresentationMode.inline,
previousPresentationMode: PresentationMode.inline,
previousPresentationMode: PresentationMode.fullscreen,
} as PresentationModeChangeEvent);
player.presentationMode = PresentationMode.inline;

Expand Down
72 changes: 46 additions & 26 deletions e2e/src/utils/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const waitForPlayerEvent = async <EType extends Event<PlayerEventType>>(
return waitForPlayerEvents(player, [expectedEvent], false, options);
};

let eventListIndex = 0; // increments for every playerEvent list that is evaluated.
export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
player: THEOplayer,
expectedEvents: Partial<EType>[],
Expand All @@ -73,39 +74,58 @@ export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
player.removeEventListener(PlayerEventType.ERROR, onError);
reject(err);
};
let eventMap = expectedEvents.map((_expected: Partial<EType>) => ({
event: _expected as Event<PlayerEventType>,
onEvent(receivedEvent: Event<PlayerEventType>) {
if (!eventMap.length) {
// No more events expected
return;
}
const expectedEvent = eventMap[0].event;

const TAG: string = `[waitForPlayerEvents] eventList ${eventListIndex}:`;
eventListIndex += 1;

let unReceivedEvents = [...expectedEvents];
const uniqueEventTypes = [...new Set(unReceivedEvents.map((event) => event.type))];
uniqueEventTypes.forEach((eventType) => {
const onEvent = (receivedEvent: Event<PlayerEventType>) => {
receivedEvents.push(receivedEvent);
console.debug('[waitForPlayerEvents]', `Received event ${JSON.stringify(receivedEvent.type)} - waiting for ${expectedEvent.type}`);
const index = eventMap.findIndex((e) => propsMatch(e.event, receivedEvent));
const isExpected = index <= 0;

// Check order
if (inOrder && eventMap.length && !isExpected) {
const err = `Expected event '${expectedEvent.type}' but received '${receivedEvent.type}'`;
console.error('[waitForPlayerEvents]', err);
reject(err);
if (inOrder && unReceivedEvents.length) {
const expectedEvent = unReceivedEvents[0];
console.debug(TAG, `Handling received event ${JSON.stringify(receivedEvent)}`);
console.debug(TAG, `Was waiting for ${JSON.stringify(expectedEvent)}`);

// Received events must either not be in the expected, or be the first
const index = unReceivedEvents.findIndex((e) => propsMatch(e, receivedEvent));
if (index > 0) {
const err = `Expected '${expectedEvent.type}' event but received '${receivedEvent.type} event'`;
console.error(TAG, err);
reject(err);
} else {
console.debug(TAG, `Received ${receivedEvent.type} event is allowed.`);
}
}
eventMap = eventMap.filter((entry) => {
if (entry.event.type === expectedEvent.type) {
player.removeEventListener(expectedEvent.type, entry.onEvent);

unReceivedEvents = unReceivedEvents.filter((event) => {
// When found, remove the listener
if (propsMatch(event, receivedEvent)) {
console.debug(TAG, ` -> removing: ${JSON.stringify(event)}`);
return false;
}
return entry.event.type !== expectedEvent.type;
// Only keep the unreceived events
console.debug(TAG, ` -> keeping: ${JSON.stringify(event)}`);
return true;
});
if (!eventMap.length) {
// Done

// remove listener if no other unreceived events require it.
if (!unReceivedEvents.find((event) => event.type === receivedEvent.type)) {
console.debug(TAG, `Removing listener for ${receivedEvent.type} from player`);
player.removeEventListener(receivedEvent.type, onEvent);
}

if (!unReceivedEvents.length) {
// Finished
resolve(receivedEvents);
}
},
}));
};

player.addEventListener(eventType as PlayerEventType, onEvent);
console.debug(TAG, `Added listener for ${eventType} to the player`);
});
player.addEventListener(PlayerEventType.ERROR, onError);
eventMap.forEach(({ event, onEvent }) => player.addEventListener(event.type, onEvent));
}),
options.timeout,
expectedEvents,
Expand Down
14 changes: 11 additions & 3 deletions example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
<uses-feature
android:name="android.software.leanback"
android:required="false" />

<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />

<!--
Allows a regular application to use Service.startForeground with the type "dataSync".
This is a requirement for the Cache API. We do not include the permission as part of the SDK
in case the Cache API is not a required feature.
-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<application
android:name=".MainApplication"
android:allowBackup="false"
android:banner="@mipmap/ic_banner"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:supportsRtl="true">
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".MainActivity"
Expand Down
1 change: 0 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ const playerConfig: PlayerConfiguration = {
skipBackwardInterval: 10,
convertSkipToSeek: true,
},
enableTHEOlive: true,
};

/**
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-theoplayer",
"version": "8.11.0",
"version": "8.11.1",
"description": "A THEOplayer video component for react-native.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
1 change: 1 addition & 0 deletions src/api/config/PlayerConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface PlayerConfiguration {
* Sets whether support for THEOlive sources should be enabled.
*
* @defaultValue `false`.
* @deprecated: THEOlive support is always enabled, there is no need to explicitly enable it.
*/
enableTHEOlive?: boolean;
}
Expand Down
14 changes: 2 additions & 12 deletions src/internal/THEOplayerView.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,13 @@ import React, { useEffect, useRef } from 'react';
import type { THEOplayerViewProps } from 'react-native-theoplayer';
import { Player, ChromelessPlayer, PlayerConfiguration } from 'theoplayer';
import { THEOplayerWebAdapter } from './adapter/THEOplayerWebAdapter';
import { registerServiceWorker, browserCanPlayHLSAndHasNoMSE } from './utils/ServiceWorkerUtils';

export function THEOplayerView(props: React.PropsWithChildren<THEOplayerViewProps>) {
const { config, children } = props;
const player = useRef<ChromelessPlayer | null>(null);
const adapter = useRef<THEOplayerWebAdapter | null>(null);
const container = useRef<null | HTMLDivElement>(null);

const preparePlayer = async (adapter: THEOplayerWebAdapter) => {
if (config?.enableTHEOlive == true && browserCanPlayHLSAndHasNoMSE()) {
await registerServiceWorker(props.config?.libraryLocation);
}

// Notify the player is ready
props.onPlayerReady?.(adapter);
};

useEffect(() => {
// Create player inside container.
if (container.current) {
Expand Down Expand Up @@ -59,8 +49,8 @@ export function THEOplayerView(props: React.PropsWithChildren<THEOplayerViewProp
// @ts-ignore
window.nativePlayer = player;

// Prepare & notify
void preparePlayer(adapter.current);
// Notify the player is ready
props.onPlayerReady?.(adapter.current);
}

// Clean-up
Expand Down
8 changes: 6 additions & 2 deletions src/internal/adapter/web/WebPresentationModeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ export class WebPresentationModeManager {
private prepareForPresentationModeChanges() {
const elements = this._player.element.children;
for (const element of Array.from(elements)) {
if (element.tagName === 'VIDEO' && element.attributes.getNamedItem('src') !== null) {
this._element = element as HTMLVideoElement;
if (element.tagName === 'VIDEO') {
const videoElement = element as HTMLVideoElement;
if ((videoElement.src !== null && videoElement.src !== '') || videoElement.srcObject !== null) {
this._element = videoElement;
break;
}
}
}
// listen for pip updates on element
Expand Down
39 changes: 0 additions & 39 deletions src/internal/utils/ServiceWorkerUtils.ts

This file was deleted.

0 comments on commit 8e96427

Please sign in to comment.