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

Feature/theolive sw #456

Merged
merged 5 commits into from
Dec 4, 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

###

- Added a `enableTHEOlive` flag to `PlayerConfiguration` to enable play-out of THEOlive sources.

## [8.9.0] - 24-11-29

### Added
Expand Down
1 change: 1 addition & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const playerConfig: PlayerConfiguration = {
skipBackwardInterval: 10,
convertSkipToSeek: true,
},
enableTHEOlive: true,
};

/**
Expand Down
6 changes: 6 additions & 0 deletions example/web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const CopyWebpackPluginConfig = new CopyWebpackPlugin({
from: path.resolve(projectDirectory, './node_modules/theoplayer/THEOplayer.transmux.*').replace(/\\/g, '/'),
to: `${libraryLocation}/[name][ext]`,
},
{
// Copy service worker
// THEOplayer will find them by setting `libraryLocation` in the playerConfiguration.
from: path.resolve(projectDirectory, './node_modules/theoplayer/theoplayer.sw.js').replace(/\\/g, '/'),
to: `${libraryLocation}/[name][ext]`,
},
{
// Copy CSS files
from: path.resolve(appDirectory, './web/public/*.css').replace(/\\/g, '/'),
Expand Down
7 changes: 7 additions & 0 deletions src/api/config/PlayerConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ export interface PlayerConfiguration {
* @defaultValue `false`.
*/
useMedia3?: boolean;

/**
* Sets whether support for THEOlive sources should be enabled.
*
* @defaultValue `false`.
*/
enableTHEOlive?: boolean;
}

/**
Expand Down
14 changes: 12 additions & 2 deletions src/internal/THEOplayerView.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ 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 @@ -49,8 +59,8 @@ export function THEOplayerView(props: React.PropsWithChildren<THEOplayerViewProp
// @ts-ignore
window.nativePlayer = player;

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

// Clean-up
Expand Down
39 changes: 39 additions & 0 deletions src/internal/utils/ServiceWorkerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export function browserCanPlayHLSAndHasNoMSE() {
const videoElement = document.createElement('video');
const canPlayHls = videoElement && videoElement.canPlayType && videoElement.canPlayType('application/vnd.apple.mpegURL') !== '';
// @ts-ignore
const canPlayMse = Boolean(window.MediaSource || window.WebKitMediaSource || window.ManagedMediaSource);
return canPlayHls && !canPlayMse;
}

export async function registerServiceWorker(libraryLocation?: string): Promise<void> {
if ('serviceWorker' in navigator) {
try {
const serviceWorkerName = 'theoplayer.sw.js';
const serviceWorkerPath = libraryLocation
? (libraryLocation.endsWith('/') ? libraryLocation : `${libraryLocation}/`) + serviceWorkerName
: serviceWorkerName;
const serviceWorkerScope = libraryLocation ? (libraryLocation.endsWith('/') ? libraryLocation : `${libraryLocation}/`) : '/';

// unregister beforehand to solve an issue when doing a hard reload of the page causing the service worker to not intercept the manifests.
await maybeUnregisterServiceWorker(serviceWorkerPath);
await navigator.serviceWorker.register(serviceWorkerPath, {
scope: serviceWorkerScope,
});
//console.log('Successfully registered server worker');
} catch (error) {
console.error(`Service worker registration failed: ${error}`);
}
}
}

export async function maybeUnregisterServiceWorker(serviceWorkerPath?: string): Promise<void> {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.getRegistration(serviceWorkerPath);
await registration?.unregister();
} catch (error) {
console.error(`Service worker unregistration failed: ${error}`);
}
}
}
Loading