diff --git a/README.md b/README.md index a9d3a99..7fb09b9 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ -# @mkhuda/react-shaka-player [![Build Status](https://app.travis-ci.com/mkhuda/react-shaka-player.svg?branch=main)](https://app.travis-ci.com/mkhuda/react-shaka-player) [![npm version](https://badge.fury.io/js/%40mkhuda%2Freact-shaka-player.svg)](https://badge.fury.io/js/%40mkhuda%2Freact-shaka-player) +# @mkhuda/react-shaka-player [![Build Status](https://app.travis-ci.com/mkhuda/react-shaka-player.svg?branch=main)](https://app.travis-ci.com/mkhuda/react-shaka-player) [![npm version](https://badge.fury.io/js/%40mkhuda%2Freact-shaka-player.svg)](https://badge.fury.io/js/%40mkhuda%2Freact-shaka-player) ![npm](https://img.shields.io/npm/v/shaka-player?label=shaka-player) -React video player built with [Shaka Player](https://github.com/google/shaka-player). [ROADMAP](https://github.com/mkhuda/react-shaka-player/wiki/Initial-Roadmap) +React video player built on top of [Shaka Player](https://github.com/google/shaka-player). +> [ROADMAP](https://github.com/mkhuda/react-shaka-player/wiki/Initial-Roadmap) + +> [EXAMPLE & USAGES](https://github.com/mkhuda/react-shaka-player/wiki/Usages-&-Examples) ## Installation Use the package manager [yarn](https://classic.yarnpkg.com/en/) or [npm](https://www.npmjs.com/) to install `react-shaka-player`. ```bash -yarn add @mkhuda/react-shaka-player +yarn add @mkhuda/react-shaka-player shaka-player or -npm install @mkhuda/react-shaka-player +npm install @mkhuda/react-shaka-player shaka-player ``` ## Usage ```javascript +// don't forget to import controls.css from shaka-player lib import "shaka-player/dist/controls.css"; -import Player from "@mkhuda/react-shaka-player"; +import ReactShakaPlayer from "@mkhuda/react-shaka-player"; function App() { - return ; + return ; } ``` @@ -56,6 +60,21 @@ function App() { } ``` +## Props + +This is main props for the components: + +| |Description |Type | +|----------------|-------------------------------|-----------------------------| +|src|MPD or HLS to play |string | +|config |Changes configuration settings on Shaka Player. Reference: [shaka.extern.PlayerConfiguration](https://shaka-player-demo.appspot.com/docs/api/shaka.extern.html#.PlayerConfiguration) | object | +|uiConfig |Changes configuration settings for UI elements. Reference: [shaka.extern.UIConfiguration](https://shaka-player-demo.appspot.com/docs/api/shaka.extern.html#.UIConfiguration) | object | +|onLoad |Catch `Shaka.Player`, `Shaka.ui.Overlay` and `HTMLVideoElement` for manual usages or improvement of configuration. see: [PlayerRefs](https://github.com/mkhuda/react-shaka-player/blob/c4459e31027a08165007d03c9a08ff8a3e5de3dc/src/types/index.ts#L3) |object: PlayerRefs => func| +|onPlay|Catch when media is playing |func| +|onPlause|Catch when media is paused |func| +|onBuffering |Catch `onBuffering` status when playing |bool => func| +|onPlayerError |Catch `error` when playing. Reference: [Shaka.Player.ErrorEvent](https://shaka-player-demo.appspot.com/docs/api/shaka.Player.html#.event:ErrorEvent) |{Shaka.extern.Error} => func| + ## Contributing Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. diff --git a/demo/index.js b/demo/index.js index 017bf84..950598d 100644 --- a/demo/index.js +++ b/demo/index.js @@ -1,23 +1,16 @@ import React from "react"; import ReactDOM from "react-dom"; -import Player from "@mkhuda/react-shaka-player"; +import ReactShakaPlayer from "@mkhuda/react-shaka-player"; import "shaka-player/dist/controls.css"; function App() { - let playerRef = React.useRef(null); - // Copyright: https://dashif.org/ const video = "https://livesim.dashif.org/livesim/chunkdur_1/ato_7/testpic4_8s/Manifest300.mpd"; return (
- +
); } diff --git a/package.json b/package.json index ac91903..4d689c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mkhuda/react-shaka-player", - "version": "1.1.1", + "version": "1.1.2", "description": "React video player built with Shaka-Player", "main": "dist/cjs.js", "author": "Muhammad K. Huda", @@ -8,7 +8,8 @@ "scripts": { "build": "rimraf dist && rollup -c --perf", "start": "rimraf dist && DEV=1 rollup -c --watch", - "test": "jest" + "test": "jest", + "watch": "rollup -c --watch" }, "dependencies": { "rollup": "^2.56.2", @@ -24,11 +25,15 @@ "@rollup/plugin-node-resolve": "^13.0.4", "@rollup/plugin-replace": "^3.0.0", "@rollup/plugin-typescript": "^8.2.5", + "@types/enzyme": "^3.10.9", "@types/jest": "^27.0.1", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-test-renderer": "^17.0.1", + "@wojtekmaj/enzyme-adapter-react-17": "^0.6.3", "babel-jest": "^27.0.6", + "enzyme": "^3.11.0", + "enzyme-to-json": "^3.6.2", "jest": "^27.0.6", "postcss": "^8.3.6", "prettier": "^2.3.2", diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx new file mode 100644 index 0000000..a7c6407 --- /dev/null +++ b/src/hooks/index.tsx @@ -0,0 +1,5 @@ +import usePlayer from "./usePlayer"; +import usePlayerListener from "./usePlayerListener"; +import useUIListener from "./useUIListener"; + +export { usePlayer, usePlayerListener, useUIListener }; diff --git a/src/hooks/usePlayer.tsx b/src/hooks/usePlayer.tsx new file mode 100644 index 0000000..7817b94 --- /dev/null +++ b/src/hooks/usePlayer.tsx @@ -0,0 +1,62 @@ +import * as Shaka from "shaka-player/dist/shaka-player.ui"; +import * as React from "react"; + +import { PlayerProps } from "../types/"; + +const usePlayer = ( + videoRef: React.MutableRefObject, + uiContainerRef: React.MutableRefObject, + props: PlayerProps +) => { + const [player, setPlayer] = React.useState(null); + const [ui, setUi] = React.useState(null); + + React.useEffect(() => { + Shaka.polyfill.installAll(); + + const player = new Shaka.Player(videoRef.current); + setPlayer(player); + + if (player) { + const ui = new Shaka.ui.Overlay( + player, + uiContainerRef.current, + videoRef.current + ); + setUi(ui); + } + + return () => { + player.destroy(); + if (ui) { + ui.destroy(); + } + }; + }, []); + + React.useEffect(() => { + if (player && props.onLoad) { + props.onLoad({ + player: player, + ui: ui, + videoElement: videoRef.current, + }); + } + }, [player]); + + React.useEffect(() => { + if (player && props.config) { + player.configure(props.config); + } + }, [player, props.config]); + + React.useEffect(() => { + if (player && props.src && Shaka.Player.isBrowserSupported()) { + player.load(props.src); + } + }, [player, props.src]); + + return { player, ui }; +}; + +export default usePlayer; diff --git a/src/hooks/usePlayerListener.tsx b/src/hooks/usePlayerListener.tsx new file mode 100644 index 0000000..11747d8 --- /dev/null +++ b/src/hooks/usePlayerListener.tsx @@ -0,0 +1,18 @@ +import * as Shaka from "shaka-player/dist/shaka-player.ui"; +import * as React from "react"; + +import { PlayerProps } from "../types"; + +const usePlayerListener = (player: Shaka.Player, props: PlayerProps) => { + React.useEffect(() => { + const _onBufferingEvent = (bufferStatus: any) => { + const boolOfBuffering: boolean = bufferStatus.buffering; + props.onBuffering(boolOfBuffering); + }; + if (player && props.onBuffering) { + player.addEventListener("buffering", _onBufferingEvent); + } + }, [player]); +}; + +export default usePlayerListener; diff --git a/src/hooks/useUIListener.tsx b/src/hooks/useUIListener.tsx new file mode 100644 index 0000000..35cc6f7 --- /dev/null +++ b/src/hooks/useUIListener.tsx @@ -0,0 +1,32 @@ +import * as Shaka from "shaka-player/dist/shaka-player.ui"; +import * as React from "react"; + +import { PlayerProps } from "../types"; + +const useUIListener = ( + ui: Shaka.ui.Overlay, + player: Shaka.Player, + props: PlayerProps +) => { + React.useEffect(() => { + if (ui && props.uiConfig) { + ui.configure(props.uiConfig); + } + }, [ui, props]); + + React.useEffect(() => { + if (player) { + const mediaElement = player.getMediaElement(); + const _onPlay = () => { + props.onPlay && props.onPlay(); + }; + const _onPause = () => { + props.onPause && props.onPause(); + }; + mediaElement.addEventListener("play", _onPlay); + mediaElement.addEventListener("pause", _onPause); + } + }, [player]); +}; + +export default useUIListener; diff --git a/src/index.tsx b/src/index.tsx index e11a300..0ca699b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,68 +1,40 @@ -import * as Shaka from "shaka-player/dist/shaka-player.ui"; import * as React from "react"; +import * as Hooks from "./hooks"; import { PlayerProps } from "./types/"; const Player = (props: PlayerProps) => { - const uiContainerRef = React.useRef(null); const videoRef = React.useRef(null); + const uiContainerRef = React.useRef(null); - const [player, setPlayer] = React.useState(null); - const [ui, setUi] = React.useState(null); - - React.useEffect(() => { - Shaka.polyfill.installAll(); - - const player = new Shaka.Player(videoRef.current); - setPlayer(player); - - if (player) { - const ui = new Shaka.ui.Overlay( - player, - uiContainerRef.current, - videoRef.current - ); - setUi(ui); - if (props.onLoad) { - props.onLoad({ - player: player, - ui: ui, - videoElement: videoRef.current, - }); - } - } - - return () => { - player.destroy(); - if (ui) { - ui.destroy(); - } - }; - }, []); - - React.useEffect(() => { - if (player && props.config) { - player.configure(props.config); - } - }, [player, props.config]); - - React.useEffect(() => { - if (player && props.src && Shaka.Player.isBrowserSupported()) { - player.load(props.src); - } - }, [player, props.src]); - - const { className, playerClassName, onLoad, ...newProps } = props; + const { player, ui } = Hooks.usePlayer(videoRef, uiContainerRef, props); + Hooks.usePlayerListener(player, props); + Hooks.useUIListener(ui, player, props); + + const { + className, + playerClassName, + config, + uiConfig, + onLoad, + onPlay, + onPause, + onBuffering, + onPlayerError, + ...newProps + } = props; + + const style = { + maxWidth: "100%", + width: "100%", + }; return ( -
+
diff --git a/src/types/index.ts b/src/types/index.ts index 7ce62e6..47d2178 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,10 +8,16 @@ export interface PlayerRefs { export interface PlayerProps { src?: string; - config?: any; + config?: shaka.extern.PlayerConfiguration | any; + uiConfig?: shaka.extern.UIConfiguration | any; autoPlay?: boolean | undefined; + playsInline?: boolean | undefined; children?: any; className?: string; playerClassName?: string; onLoad?(data: PlayerRefs): void; + onPlay?(): void | undefined; + onPause?(): void | undefined; + onPlayerError?(event: Shaka.extern.Error): void; + onBuffering?(event: boolean): void; } diff --git a/test/__snapshots__/app.test.tsx.snap b/test/__snapshots__/app.test.tsx.snap index 6090020..99ae29d 100644 --- a/test/__snapshots__/app.test.tsx.snap +++ b/test/__snapshots__/app.test.tsx.snap @@ -1,67 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Player render className container correctly 1`] = ` -
-
-`; +exports[`Player render className container correctly 1`] = `ShallowWrapper {}`; -exports[`Player render container className and playerClassName container correctly 1`] = ` -
-
-`; +exports[`Player render container className and playerClassName container correctly 1`] = `ShallowWrapper {}`; -exports[`Player render playerClassName container correctly 1`] = ` -
-
-`; +exports[`Player render playerClassName container correctly 1`] = `ShallowWrapper {}`; exports[`Player renders correctly 1`] = ` -
-
-`; - -exports[`Player renders source[src] string correctly 1`] = ` -
+ } +>