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.8.0 #440

Merged
merged 36 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1e5621c
Wrap a viewController around the THEOplayerView
wvanhaevre Nov 8, 2024
0ba414f
Use the wrapping theoPlayerViewController to reparent all underlying …
wvanhaevre Nov 8, 2024
2c1c3df
Add changelog entry
wvanhaevre Nov 8, 2024
a6a18d1
Introduce HomeIndicatorViewController
wvanhaevre Nov 12, 2024
c6f8990
Make HomeViewController part of the pod
wvanhaevre Nov 12, 2024
99e3bb3
When rootViewController is of HomeIndicatorViewController type, toggl…
wvanhaevre Nov 12, 2024
cc1a2e1
Update ios setup for native example app to latest API
wvanhaevre Nov 12, 2024
c628364
Setup the example app's rootViewController as a HomeIndicatorViewCont…
wvanhaevre Nov 12, 2024
f16e07f
update pod locks and ios project
wvanhaevre Nov 12, 2024
ce1c318
Add docs update on the Home indicator show/hide functionality
wvanhaevre Nov 12, 2024
05698e5
Add changelog entry
wvanhaevre Nov 12, 2024
15782d9
Fix waiting for player to be ready
tvanlaerhoven Nov 5, 2024
18d9cb3
Change test order
tvanlaerhoven Nov 5, 2024
be0c63a
Add cavy log patch
tvanlaerhoven Nov 5, 2024
019bb05
Update iOS workflow
tvanlaerhoven Nov 14, 2024
3548bf1
Merge pull request #434 from THEOplayer/maintenance/e2e_await_player
tvanlaerhoven Nov 14, 2024
a752eb8
Wrap a viewController around the THEOplayerView
wvanhaevre Nov 8, 2024
09a478f
Use the wrapping theoPlayerViewController to reparent all underlying …
wvanhaevre Nov 8, 2024
03078c7
Add changelog entry
wvanhaevre Nov 8, 2024
dac0e16
Do not restart media service when a media button event is received
tvanlaerhoven Nov 14, 2024
40c825e
Add changelog entry
tvanlaerhoven Nov 15, 2024
305af34
Merge branch 'feature/simplify-fullscreen-view-reparenting' into feat…
wvanhaevre Nov 15, 2024
de5e404
Merge pull request #439 from THEOplayer/bugfix/android_dont_restart_s…
tvanlaerhoven Nov 18, 2024
8b3e709
Add consumer R8 config file
tvanlaerhoven Nov 14, 2024
a9d6e3c
Add changelog entry
tvanlaerhoven Nov 14, 2024
fb2c9e4
Enable R8 minifying in the e2e app
tvanlaerhoven Nov 14, 2024
ae9eab2
Merge pull request #438 from THEOplayer/feature/android_consumer_r8
tvanlaerhoven Nov 18, 2024
b6de479
Wrap a viewController around the THEOplayerView
wvanhaevre Nov 8, 2024
26aef02
Use the wrapping theoPlayerViewController to reparent all underlying …
wvanhaevre Nov 8, 2024
035b9a2
Add changelog entry
wvanhaevre Nov 8, 2024
2c8aa0a
Merge pull request #436 from THEOplayer/feature/simplify-fullscreen-v…
tvanlaerhoven Nov 18, 2024
50e8e34
Merge branch 'develop' into feature/ios-hide-home-indicator-in-fullsc…
wvanhaevre Nov 19, 2024
976ab8f
Patch cavy with proper failure descriptions
tvanlaerhoven Nov 19, 2024
7209442
Add extends event logging for e2e tests
tvanlaerhoven Nov 19, 2024
95b42ec
Merge pull request #437 from THEOplayer/feature/ios-hide-home-indicat…
tvanlaerhoven Nov 19, 2024
6343f74
Update changelog
tvanlaerhoven Nov 19, 2024
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
12 changes: 6 additions & 6 deletions .github/workflows/pr_ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ jobs:
build:
strategy:
matrix:
xcode_version: [ '15.2' ]
runs-on: macos-14
xcode_version: [ '15.4.0' ]
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Select Xcode ${{ matrix.xcode_version }}
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ matrix.xcode_version }} # Check versions: https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
xcode-version: ${{ matrix.xcode_version }}

- name: Setup Node
uses: actions/setup-node@v4
Expand Down Expand Up @@ -50,10 +50,10 @@ jobs:
pod update

- name: Start iOS simulator
uses: futureware-tech/simulator-action@v3
uses: futureware-tech/simulator-action@v4
with:
model: 'iPhone 14'
os_version: '>=16.0'
model: 'iPhone 15'
os_version: '>=14.0'

- name: Run e2e tests
working-directory: e2e
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ 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.8.0] - 24-11-19

### Added

- Added `HomeIndicatorViewController` to iOS, which can be used as an alternative `rootViewController` for the native App. It will automatically show/hide the home indicator when transitioning from/to fullscreen presentationMode.

### Changed

- Simplified the `viewController` reparenting mechanism on iOS that is applied when changing the presentationMode to/from fullscreen.
- The `MediaPlaybackService` on Android is never restarted if a MediaButton event is received after the app was closed.
- Added a consumer R8 config file on Android, telling R8 not to throw errors or warnings because of classes that are expected to be missing.

## [8.7.0] - 24-11-05

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ android {
buildConfigField "boolean", "EXTENSION_ADS", "${enabledAds}"
buildConfigField "boolean", "EXTENSION_CAST", "${enabledCast}"
buildConfigField "boolean", "EXTENSION_MEDIASESSION", "${enabledMediaSession}"

consumerProguardFiles 'proguard-rules.pro'
}

buildTypes {
Expand Down
3 changes: 3 additions & 0 deletions android/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Do no warn if any of the API classes we resolve with compileOnly are missing because the feature
# is disabled: it is expected.
-dontwarn com.theoplayer.android.api.**
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ class ReactTHEOplayerContext private constructor(
// Create and initialize the media session
val mediaSession = MediaSessionCompat(reactContext, TAG)

// Do not let MediaButtons restart the player when the app is not visible
// Do not let MediaButtons restart the player when media session is not active.
// https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
mediaSession.setMediaButtonReceiver(null)

// Create a MediaSessionConnector and attach the THEOplayer instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ class MediaPlaybackService : Service() {

// Set mediaSession active
setActive(BuildConfig.EXTENSION_MEDIASESSION)

// Do not let MediaButtons restart the player when media session is not active.
// https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
mediaSession.setMediaButtonReceiver(null)
}
}

Expand Down
25 changes: 25 additions & 0 deletions doc/fullscreen.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,31 @@ player.presentationMode = PresentationMode.fullscreen;

When the player transitions back to inline mode, the view hierarchy will be restored.

### iOS home indicator

On iOS, a visual bar at the bottom of the screen called the home indicator, allows the user to return to the device's home screen when dragging it up. This is often preceived as a disturbing visual element when viewing a stream in fullscreen mode. To hide the home indicator, The rootViewController of the application needs to be setup accordingly, via inheritance. As part of react-native-theoplayer we've prepared a basic ViewController setup (HomeIndicatorViewController) that takes care of this. To hide the home indicator you change the default rootViewcontroller from a basic UIViewController to our HomeIndicatorViewController:

Import the react-native-theoplayer swift code:
```swift
@import react_native_theoplayer;
```

And, when using RCTAppDelegate in the native app:
```swift
- (UIViewController *)createRootViewController {
return [HomeIndicatorViewController new];
}
```

or otherwise:
```swift
HomeIndicatorViewController *rootViewController = [HomeIndicatorViewController new];
...
self.window.rootViewController = rootViewController;
```

Our iOS presentationMode changing code checks if the rootViewController is of type HomeIndicatorViewController and will, in that case, automatically take care of showing/hiding the home indicator.

## Portals

A [Portal](https://react.dev/reference/react-dom/createPortal#usage) is a well-known concept in React that
Expand Down
2 changes: 1 addition & 1 deletion e2e/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ react {
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
def enableProguardInReleaseBuilds = true

/**
* The preferred build flavor of JavaScriptCore (JSC)
Expand Down
68 changes: 68 additions & 0 deletions e2e/patches/cavy+4.0.2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
diff --git a/node_modules/cavy/src/Reporter.js b/node_modules/cavy/src/Reporter.js
index 1cdeb38..f7e9e35 100644
--- a/node_modules/cavy/src/Reporter.js
+++ b/node_modules/cavy/src/Reporter.js
@@ -9,7 +9,15 @@ export default class Reporter {
// Internal: Creates a websocket connection to the cavy-cli server.
onStart() {
const url = 'ws://127.0.0.1:8082/';
+ console.debug('Creating websocket');
this.ws = new WebSocket(url);
+ this.ws.onerror = console.error
+ this.ws.onopen = () => {
+ console.debug('Successfully opened websocket');
+ }
+ this.ws.onclose = () => {
+ console.debug('Closing websocket');
+ }
}

// Internal: Send a single test result to cavy-cli over the websocket connection.
@@ -34,7 +42,6 @@ export default class Reporter {
console.log(message);
}
}
-
// Private: Determines whether data can be sent over the websocket.
websocketReady() {
// WebSocket.readyState 1 means the web socket connection is OPEN.
diff --git a/node_modules/cavy/src/TestRunner.js b/node_modules/cavy/src/TestRunner.js
index 40552bf..5b98f72 100644
--- a/node_modules/cavy/src/TestRunner.js
+++ b/node_modules/cavy/src/TestRunner.js
@@ -148,7 +148,7 @@ export default class TestRunner {
const stop = new Date();
const time = (stop - start) / 1000;

- let fullErrorMessage = `${description} ❌\n ${e.message}`;
+ let fullErrorMessage = `${description} ❌\n ${JSON.stringify(e)}`;
console.warn(fullErrorMessage);

this.results.push({
diff --git a/node_modules/cavy/src/Tester.js b/node_modules/cavy/src/Tester.js
index c61e31a..8d222ae 100644
--- a/node_modules/cavy/src/Tester.js
+++ b/node_modules/cavy/src/Tester.js
@@ -57,20 +57,8 @@ export default class Tester extends Component {
key: Math.random()
};
this.testHookStore = props.store;
- // Default to sending a test report to cavy-cli if no custom reporter is
- // supplied.
- if (props.reporter instanceof Function) {
- const message = 'Deprecation warning: support for custom function' +
- 'reporters will soon be deprecated. Cavy supports custom ' +
- 'class based reporters. For more info, see the ' +
- 'documentation here: ' +
- 'https://cavy.app/docs/guides/writing-custom-reporters';
- console.warn(message);
- this.reporter = props.reporter;
- } else {
- reporterClass = props.reporter || Reporter;
- this.reporter = new reporterClass;
- }
+ reporterClass = props.reporter || Reporter;
+ this.reporter = new reporterClass;
}

componentDidMount() {
34 changes: 28 additions & 6 deletions e2e/src/components/TestableTHEOplayerView.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,45 @@
import { useCavy } from 'cavy';
import { THEOplayer, THEOplayerView, THEOplayerViewProps } from 'react-native-theoplayer';
import React, { useCallback } from 'react';
import { PromiseController } from '../utils/PromiseController';

let playerController = new PromiseController<THEOplayer>();
let testPlayer: THEOplayer | undefined = undefined;

export const getTestPlayer = async (): Promise<THEOplayer> => {
return playerController.promise_;
/**
* Wait until the player is ready.
*
* @param timeout Delay after rejecting the player.
* @param poll Delay before trying again.
*/
export const getTestPlayer = async (timeout = 5000, poll = 200): Promise<THEOplayer> => {
return new Promise((resolve, reject) => {
const start = Date.now();
const checkPlayer = () => {
setTimeout(() => {
if (testPlayer) {
// Player is ready.
resolve(testPlayer);
} else if (Date.now() - start > timeout) {
// Too late.
reject('Player not ready');
} else {
// Wait & try again.
checkPlayer();
}
}, poll);
};
checkPlayer();
});
};

export const TestableTHEOplayerView = (props: THEOplayerViewProps) => {
const generateTestHook = useCavy();
const onPlayerReady = useCallback((player: THEOplayer) => {
playerController.resolve_(player);
testPlayer = player;
props.onPlayerReady?.(player);
}, []);

const onPlayerDestroy = useCallback(() => {
playerController = new PromiseController<THEOplayer>();
testPlayer = undefined;
}, []);

return <THEOplayerView ref={generateTestHook('Scene.THEOplayerView')} {...props} onPlayerReady={onPlayerReady} onPlayerDestroy={onPlayerDestroy} />;
Expand Down
2 changes: 1 addition & 1 deletion e2e/src/tests/Basic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import hls from '../res/hls.json';
import mp4 from '../res/mp4.json';
import { expect, preparePlayerWithSource, waitForPlayerEventType, waitForPlayerEventTypes } from '../utils/Actions';

const SEEK_THRESHOLD = 1e-1;
const SEEK_THRESHOLD = 250;

function testBasicPlayout(spec: TestScope, title: string, source: SourceDescription) {
spec.describe(title, function () {
Expand Down
2 changes: 1 addition & 1 deletion e2e/src/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import Connector from './Connector.spec';
import PresentationMode from './PresentationMode.spec';
import Version from './Version.spec';

export default [Basic, Ads, Connector, PresentationMode, Version];
export default [Version, Basic, Ads, Connector, PresentationMode];
24 changes: 17 additions & 7 deletions e2e/src/utils/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
inOrder: boolean = true,
options = defaultTestOptions,
): Promise<Event<PlayerEventType>[]> => {
return withTimeOut(
const receivedEvents: Event<PlayerEventType>[] = [];
return withEventTimeOut(
new Promise<Event<PlayerEventType>[]>((resolve, reject) => {
const events: Event<PlayerEventType>[] = [];
const onError = (err: ErrorEvent) => {
console.error('[waitForPlayerEvents]', err);
player.removeEventListener(PlayerEventType.ERROR, onError);
Expand All @@ -81,14 +81,14 @@ export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
return;
}
const expectedEvent = eventMap[0].event;
events.push(receivedEvent);
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}'\nbut received '${receivedEvent.type}'`;
const err = `Expected event '${expectedEvent.type}' but received '${receivedEvent.type}'`;
console.error('[waitForPlayerEvents]', err);
reject(err);
}
Expand All @@ -100,21 +100,31 @@ export const waitForPlayerEvents = async <EType extends Event<PlayerEventType>>(
});
if (!eventMap.length) {
// Done
resolve(events);
resolve(receivedEvents);
}
},
}));
player.addEventListener(PlayerEventType.ERROR, onError);
eventMap.forEach(({ event, onEvent }) => player.addEventListener(event.type, onEvent));
}),
options.timeout,
expectedEvents,
receivedEvents,
);
};

const withTimeOut = (promise: Promise<any>, timeout: number): Promise<any> => {
const withEventTimeOut = <EType extends Event<PlayerEventType>>(
promise: Promise<any>,
timeout: number,
expectedEvents: Partial<EType>[],
receivedEvents: EType[],
): Promise<any> => {
return new Promise<void>((resolve, reject) => {
const handle = setTimeout(() => {
reject('Timeout waiting for event');
reject(
`Timeout waiting for next event, expecting [${expectedEvents.map((ev) => JSON.stringify(ev)).join(',')}] ` +
`already received [${receivedEvents.map((ev) => JSON.stringify(ev)).join(',')}]`,
);
}, timeout);
promise
.then((result: any) => {
Expand Down
Loading
Loading