Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/v8.11.1 #465

Merged
merged 16 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading