Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/player-playback-speed

This commit is contained in:
Tim 2022-11-26 14:02:17 +01:00
commit b60d03a3d7
15 changed files with 1753 additions and 51 deletions

BIN
favicons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
images/icon_x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/icon_x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

40
manifest.json Normal file
View file

@ -0,0 +1,40 @@
{
"short_name": "Stremio",
"name": "Stremio Web",
"description": "Freedom To Stream",
"icons": [
{
"src": "favicons/favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "images/icon_x192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "images/icon_x512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "images/maskable_icon_x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "images/maskable_icon_x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": "https://web.stremio.com",
"scope": "https://web.stremio.com",
"display": "standalone",
"orientation": "natural",
"theme_color": "#2a2843",
"background_color": "#161523"
}

1667
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,16 +14,17 @@
"dependencies": { "dependencies": {
"@babel/runtime": "7.16.0", "@babel/runtime": "7.16.0",
"@sentry/browser": "6.13.3", "@sentry/browser": "6.13.3",
"@stremio/stremio-colors": "4.0.1", "@stremio/stremio-colors": "5.0.1",
"@stremio/stremio-core-web": "0.44.6", "@stremio/stremio-core-web": "0.44.6",
"@stremio/stremio-icons": "4.0.0", "@stremio/stremio-icons": "4.0.0",
"@stremio/stremio-video": "0.0.23", "@stremio/stremio-video": "0.0.24",
"a-color-picker": "1.2.1", "a-color-picker": "1.2.1",
"bowser": "2.11.0", "bowser": "2.11.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"classnames": "2.3.1", "classnames": "2.3.1",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"filter-invalid-dom-props": "2.1.0", "filter-invalid-dom-props": "2.1.0",
"hat": "0.0.3",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",
"lodash.intersection": "4.4.0", "lodash.intersection": "4.4.0",
"lodash.isequal": "4.5.0", "lodash.isequal": "4.5.0",
@ -61,6 +62,7 @@
"terser-webpack-plugin": "5.2.4", "terser-webpack-plugin": "5.2.4",
"webpack": "5.61.0", "webpack": "5.61.0",
"webpack-cli": "4.9.1", "webpack-cli": "4.9.1",
"webpack-dev-server": "4.7.4" "webpack-dev-server": "4.7.4",
"workbox-webpack-plugin": "^6.5.3"
} }
} }

View file

@ -7,7 +7,9 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Stremio"> <meta name="apple-mobile-web-app-title" content="Stremio">
<link rel="icon" type="image/png" sizes="96x96" href="<%= htmlWebpackPlugin.options.faviconsPath %>/icon-96.png"> <link rel="icon" type="image/png" sizes="96x96" href="<%= htmlWebpackPlugin.options.faviconsPath %>/icon-96.png">
<link rel="apple-touch-icon" href="<%= htmlWebpackPlugin.options.faviconsPath %>/icon-96.png" /> <link rel="manifest" href="<%= htmlWebpackPlugin.options.manifestPath %>" />
<meta name="theme-color" content="<%= htmlWebpackPlugin.options.themeColor %>">
<link rel="apple-touch-icon" href="<%= htmlWebpackPlugin.options.imagesPath %>/icon_x192.png">
<title>Stremio - All you can watch!</title> <title>Stremio - All you can watch!</title>
<%= htmlWebpackPlugin.tags.headTags %> <%= htmlWebpackPlugin.tags.headTags %>
</head> </head>

View file

@ -17,3 +17,12 @@ const App = require('./App');
const root = ReactDOM.createRoot(document.getElementById('app')); const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />); root.render(<App />);
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.catch((registrationError) => {
console.error('SW registration failed: ', registrationError);
});
});
}

View file

@ -249,7 +249,10 @@ const Player = ({ urlParams, queryParams }) => {
[] []
}, },
autoplay: true, autoplay: true,
time: player.libraryItem !== null && player.selected.streamRequest !== null && player.libraryItem.state.video_id === player.selected.streamRequest.id ? time: player.libraryItem !== null &&
player.selected.streamRequest !== null &&
player.selected.streamRequest.path !== null &&
player.libraryItem.state.video_id === player.selected.streamRequest.path.id ?
player.libraryItem.state.timeOffset player.libraryItem.state.timeOffset
: :
0, 0,
@ -380,7 +383,7 @@ const Player = ({ urlParams, queryParams }) => {
const onKeyDown = (event) => { const onKeyDown = (event) => {
switch (event.code) { switch (event.code) {
case 'Space': { case 'Space': {
if (!subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && videoState.paused !== null) { if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen&& videoState.paused !== null) {
if (videoState.paused) { if (videoState.paused) {
onPlayRequested(); onPlayRequested();
} else { } else {
@ -391,7 +394,7 @@ const Player = ({ urlParams, queryParams }) => {
break; break;
} }
case 'ArrowRight': { case 'ArrowRight': {
if (!subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && videoState.time !== null) { if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
const seekTimeMultiplier = event.shiftKey ? 3 : 1; const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier)); onSeekRequested(videoState.time + (settings.seekTimeDuration * seekTimeMultiplier));
} }
@ -399,7 +402,7 @@ const Player = ({ urlParams, queryParams }) => {
break; break;
} }
case 'ArrowLeft': { case 'ArrowLeft': {
if (!subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && videoState.time !== null) { if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.time !== null) {
const seekTimeMultiplier = event.shiftKey ? 3 : 1; const seekTimeMultiplier = event.shiftKey ? 3 : 1;
onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier)); onSeekRequested(videoState.time - (settings.seekTimeDuration * seekTimeMultiplier));
} }
@ -407,14 +410,14 @@ const Player = ({ urlParams, queryParams }) => {
break; break;
} }
case 'ArrowUp': { case 'ArrowUp': {
if (!subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && videoState.volume !== null) { if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
onVolumeChangeRequested(videoState.volume + 5); onVolumeChangeRequested(videoState.volume + 5);
} }
break; break;
} }
case 'ArrowDown': { case 'ArrowDown': {
if (!subtitlesMenuOpen && !infoMenuOpen && !speedMenuOpen && videoState.volume !== null) { if (!subtitlesMenuOpen && !infoMenuOpen && !videosMenuOpen && !speedMenuOpen && videoState.volume !== null) {
onVolumeChangeRequested(videoState.volume - 5); onVolumeChangeRequested(videoState.volume - 5);
} }
@ -475,7 +478,7 @@ const Player = ({ urlParams, queryParams }) => {
return () => { return () => {
window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keydown', onKeyDown);
}; };
}, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, speedMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]); }, [player.metaItem, settings.seekTimeDuration, routeFocused, subtitlesMenuOpen, infoMenuOpen, videosMenuOpen, speedMenuOpen, videoState.paused, videoState.time, videoState.volume, videoState.audioTracks, videoState.subtitlesTracks, videoState.extraSubtitlesTracks, toggleSubtitlesMenu, toggleInfoMenu, toggleVideosMenu]);
React.useLayoutEffect(() => { React.useLayoutEffect(() => {
return () => { return () => {
setImmersedDebounced.cancel(); setImmersedDebounced.cancel();
@ -529,7 +532,7 @@ const Player = ({ urlParams, queryParams }) => {
null null
} }
{ {
subtitlesMenuOpen || infoMenuOpen || speedMenuOpen ? subtitlesMenuOpen || infoMenuOpen || videosMenuOpen || speedMenuOpen ?
<div className={styles['layer']} /> <div className={styles['layer']} />
: :
null null

View file

@ -20,7 +20,7 @@ function Chromecast() {
function onTransportInitError(args) { function onTransportInitError(args) {
console.error(args); console.error(args);
active = false; active = false;
error = new Error('Google Cast API not available'); error = new Error('Google Cast API not available', { cause: args });
starting = false; starting = false;
onStateChanged(); onStateChanged();
transport = null; transport = null;

View file

@ -1,6 +1,7 @@
// Copyright (C) 2017-2022 Smart code 203358507 // Copyright (C) 2017-2022 Smart code 203358507
const EventEmitter = require('eventemitter3'); const EventEmitter = require('eventemitter3');
const hat = require('hat');
const MESSAGE_NAMESPACE = 'urn:x-cast:com.stremio'; const MESSAGE_NAMESPACE = 'urn:x-cast:com.stremio';
const CHUNK_SIZE = 20000; const CHUNK_SIZE = 20000;
@ -33,7 +34,7 @@ const initialize = () => {
function ChromecastTransport() { function ChromecastTransport() {
const events = new EventEmitter(); const events = new EventEmitter();
const chunks = []; const messages = {};
initialize() initialize()
.then(() => { .then(() => {
@ -59,26 +60,17 @@ function ChromecastTransport() {
function onMessage(_, message) { function onMessage(_, message) {
try { try {
const { chunk, last } = JSON.parse(message); const { id, chunk, index, length } = JSON.parse(message);
chunks.push(chunk); messages[id] = messages[id] || [];
if (!last) { messages[id][index] = chunk;
return; if (Object.keys(messages[id]).length === length) {
const parsedMessage = JSON.parse(messages[id].join(''));
delete messages[id];
events.emit('message', parsedMessage);
} }
} catch (error) { } catch (error) {
chunks.splice(0, chunks.length);
events.emit('message-error', error); events.emit('message-error', error);
return;
} }
let parsedMessage;
try {
parsedMessage = JSON.parse(chunks.splice(0, chunks.length).join(''));
} catch (error) {
events.emit('message-error', error);
return;
}
events.emit('message', parsedMessage);
} }
function onApplicationStatusChanged(event) { function onApplicationStatusChanged(event) {
events.emit(cast.framework.CastSession.APPLICATION_STATUS_CHANGED, event); events.emit(cast.framework.CastSession.APPLICATION_STATUS_CHANGED, event);
@ -165,11 +157,13 @@ function ChromecastTransport() {
const chunk = serializedMessage.slice(start, start + CHUNK_SIZE); const chunk = serializedMessage.slice(start, start + CHUNK_SIZE);
chunks.push(chunk); chunks.push(chunk);
} }
const id = hat();
return Promise.all(chunks.map((chunk, index) => { return Promise.all(chunks.map((chunk, index) => {
return castSession.sendMessage(MESSAGE_NAMESPACE, { return castSession.sendMessage(MESSAGE_NAMESPACE, {
id,
chunk, chunk,
last: index === chunks.length - 1, index,
length: chunks.length
}); });
})); }));
} else { } else {

View file

@ -20,7 +20,7 @@ function Core(args) {
function onTransportError(args) { function onTransportError(args) {
console.error(args); console.error(args);
active = false; active = false;
error = new Error('Stremio Core Transport initialization failed'); error = new Error('Stremio Core Transport initialization failed', { cause: args });
starting = false; starting = false;
onStateChanged(); onStateChanged();
transport = null; transport = null;

View file

@ -6,8 +6,10 @@ const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin'); const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const WorkboxPlugin = require('workbox-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const colors = require('@stremio/stremio-colors');
const pachageJson = require('./package.json'); const pachageJson = require('./package.json');
const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim(); const COMMIT_HASH = execSync('git rev-parse HEAD').toString().trim();
@ -187,8 +189,18 @@ module.exports = (env, argv) => ({
new CleanWebpackPlugin({ new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['*'] cleanOnceBeforeBuildPatterns: ['*']
}), }),
argv.mode === 'production' &&
new WorkboxPlugin.GenerateSW({
maximumFileSizeToCacheInBytes: 20000000,
clientsClaim: true,
skipWaiting: true
}),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [{ from: 'favicons', to: `${COMMIT_HASH}/favicons` }] patterns: [
{ from: 'favicons', to: `${COMMIT_HASH}/favicons` },
{ from: 'images', to: `${COMMIT_HASH}/images` },
{ from: 'manifest.json', to: `${COMMIT_HASH}/manifest.json` },
]
}), }),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: `${COMMIT_HASH}/styles/[name].css` filename: `${COMMIT_HASH}/styles/[name].css`
@ -197,7 +209,10 @@ module.exports = (env, argv) => ({
template: './src/index.html', template: './src/index.html',
inject: false, inject: false,
scriptLoading: 'blocking', scriptLoading: 'blocking',
faviconsPath: `${COMMIT_HASH}/favicons` themeColor: colors.background,
faviconsPath: `${COMMIT_HASH}/favicons`,
imagesPath: `${COMMIT_HASH}/images`,
manifestPath: `${COMMIT_HASH}/manifest.json`,
}) })
] ].filter(Boolean)
}); });