Skip to content
This repository has been archived by the owner on Mar 1, 2024. It is now read-only.

Minimal sample React implementation #159

Merged
merged 6 commits into from
Mar 18, 2023
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
2 changes: 1 addition & 1 deletion Frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ This will produce `player.js` under the `SignallingWebServer/Public` directory -

### Making your own UI

We recommend studying [/ui-library](/Frontend/ui-library) and [player.ts](/Frontend/implementations/EpicGames/src/player.ts)/[player.html](/Frontend/implementations/EpicGames/src/player.html), then once you have copied and modified the [package.json](/Frontend/implementations/EpicGames/package.json) and `.ts` into your own `implementation/your_implementation` directory, the process is similar:
We recommend studying [/ui-library](/Frontend/ui-library) and [player.ts](/Frontend/implementations/EpicGames/src/player.ts)/[player.html](/Frontend/implementations/EpicGames/src/player.html), or alternatively the sample React implementation in [implementations/react](/Frontend/implementations/react), then once you have copied and modified the [package.json](/Frontend/implementations/EpicGames/package.json) and `.ts` into your own `implementation/your_implementation` directory, the process is similar:

- `cd implementation/your_implementation`
- `npm build-all`
Expand Down
11 changes: 11 additions & 0 deletions Frontend/implementations/react/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
dist/
docs/
node_modules/
types/
package-lock.json
package.json
.cspell.json
tsconfig.json
webpack.config.js
.eslintrc.js
.vscode
6 changes: 6 additions & 0 deletions Frontend/implementations/react/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 4,
"semi": true,
"singleQuote": true
}
4,286 changes: 4,286 additions & 0 deletions Frontend/implementations/react/package-lock.json

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions Frontend/implementations/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@epicgames-ps/reference-pixelstreamingfrontend-react-ue5.2",
"version": "0.0.1",
"description": "",
"main": "./src/index.tsx",
"scripts": {
"build": "npx webpack --config webpack.prod.js",
"build-dev": "npx webpack --config webpack.dev.js",
"watch": "npx webpack --watch",
"serve": "webpack serve --config webpack.dev.js",
"serve-prod": "webpack serve --config webpack.prod.js",
"build-all": "npm link ../../library && cd ../../library && npm run build && cd ../implementations/react && npm run build",
"build-dev-all": "npm link ../../library && cd ../../library && npm run build-dev && cd ../implementations/react && npm run build-dev"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"css-loader": "^6.7.3",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.0",
"path": "^0.12.7",
"ts-loader": "^9.4.2",
"typescript": "^4.9.4",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
17 changes: 17 additions & 0 deletions Frontend/implementations/react/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Pixel Streaming sample React application

A minimal sample application that uses the Pixel Streaming library in React.

### Key features
- A minimal React application with a Pixel Streaming wrapper component
- Starts a Pixel Streaming session on wrapper component mount
- Disconnects the session on wrapper component unmount e.g. if navigating to another view in a single page app
- Hooks to `playStreamRejected` event and displays a `Click to play` overlay if the browser rejects video stream auto-play

### Developing

To build and run the React application, run:

- `npm install`
- `npm run build-all`
- `npm run serve`
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions Frontend/implementations/react/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.

import React from 'react';
import { PixelStreamingWrapper } from './PixelStreamingWrapper';

export const App = () => {
return (
<div
style={{
height: '100%',
width: '100%'
}}
>
<PixelStreamingWrapper
initialSettings={{
AutoPlayVideo: true,
AutoConnect: true,
ss: 'ws://localhost:80',
StartVideoMuted: true,
HoveringMouse: true
}}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright Epic Games, Inc. All Rights Reserved.

import React, { useEffect, useRef, useState } from 'react';
import {
Config,
AllSettings,
PixelStreaming
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.2';

export interface PixelStreamingWrapperProps {
initialSettings?: Partial<AllSettings>;
}

export const PixelStreamingWrapper = ({
initialSettings
}: PixelStreamingWrapperProps) => {
// A reference to parent div element that the Pixel Streaming library attaches into:
const videoParent = useRef<HTMLDivElement>(null);

// Pixel streaming library instance is stored into this state variable after initialization:
const [pixelStreaming, setPixelStreaming] = useState<PixelStreaming>();

// A boolean state variable that determines if the Click to play overlay is shown:
const [clickToPlayVisible, setClickToPlayVisible] = useState(false);

// Run on component mount:
useEffect(() => {
if (videoParent.current) {
// Attach Pixel Streaming library to videoParent element:
const config = new Config({ initialSettings });
const streaming = new PixelStreaming(config, {
videoElementParent: videoParent.current
});

// register a playStreamRejected handler to show Click to play overlay if needed:
streaming.addEventListener('playStreamRejected', () => {
setClickToPlayVisible(true);
});

// Save the library instance into component state so that it can be accessed later:
setPixelStreaming(streaming);

// Clean up on component unmount:
return () => {
try {
streaming.disconnect();
} catch {}
};
}
}, []);

return (
<div
style={{
width: '100%',
height: '100%',
position: 'relative'
}}
>
<div
style={{
width: '100%',
height: '100%'
}}
ref={videoParent}
/>
{clickToPlayVisible && (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer'
}}
onClick={() => {
pixelStreaming?.play();
setClickToPlayVisible(false);
}}
>
<div>Click to play</div>
</div>
)}
</div>
);
};
28 changes: 28 additions & 0 deletions Frontend/implementations/react/src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- Optional: apply a font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">

<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">

<!-- Optional: set a title for your page -->
<title>Pixel Streaming</title>
</head>

<!-- The Pixel Streaming player fills 100% of its parent element but body has a 0px height unless filled with content. As such, we explicitly force the body to be 100% of the viewport height -->
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">

</body>

</html>
9 changes: 9 additions & 0 deletions Frontend/implementations/react/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './components/App';

document.body.onload = function () {
// Attach the React app root component to document.body
createRoot(document.body).render(<App />);
};
16 changes: 16 additions & 0 deletions Frontend/implementations/react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"outDir": "./dist",
"noImplicitAny": true,
"module": "es6",
"esModuleInterop": true,
"target": "es5",
"moduleResolution": "node",
"sourceMap": false,
"allowJs": true,
"declaration": false,
"jsx": "react-jsx"
},
"lib": ["es2015"],
"include": ["./src/*.tsx"],
}
75 changes: 75 additions & 0 deletions Frontend/implementations/react/webpack.common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright Epic Games, Inc. All Rights Reserved.

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const fs = require('fs');

const pages = fs.readdirSync('./src', { withFileTypes: true })
.filter(item => !item.isDirectory())
.filter(item => path.parse(item.name).ext === '.html')
.map(htmlFile => path.parse(htmlFile.name).name);

module.exports = {
entry: pages.reduce((config, page) => {
config[page] = `./src/${page}.tsx`;
return config;
}, {}),

plugins: [].concat(pages.map((page) => new HtmlWebpackPlugin({
title: `${page}`,
template: `./src/${page}.html`,
filename: `${page}.html`,
chunks: [page],
}), )),

module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: [
/node_modules/,
],
},
{
test: /\.html$/i,
use: 'html-loader'
},
{
test: /\.css$/,
type: 'asset/resource',
generator: {
filename: 'css/[name][ext]'
}
},
{
test: /\.(png|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'images/[name][ext]'
}
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.svg', '.json'],
},
output: {
filename: '[name].js',
library: 'epicgames-react-frontend',
libraryTarget: 'umd',
path: path.resolve(__dirname, '../../../SignallingWebServer/Public'),
clean: true,
globalObject: 'this',
hashFunction: 'xxhash64',
},
experiments: {
futureDefaults: true
},
devServer: {
static: {
directory: path.join(__dirname, '../../../SignallingWebServer/Public'),
},
},
}
10 changes: 10 additions & 0 deletions Frontend/implementations/react/webpack.dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright Epic Games, Inc. All Rights Reserved.

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require('path');

module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map'
});
16 changes: 16 additions & 0 deletions Frontend/implementations/react/webpack.prod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright Epic Games, Inc. All Rights Reserved.

const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
mode: 'production',
optimization: {
usedExports: true,
minimize: true
},
stats: 'errors-only',
performance: {
hints: false
}
});