mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-11 08:10:40 +00:00
Merge branch 'development' of https://github.com/Stremio/stremio-web into feat/player-playback-speed
This commit is contained in:
commit
b60d03a3d7
15 changed files with 1753 additions and 51 deletions
BIN
favicons/favicon.ico
Normal file
BIN
favicons/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
images/icon_x192.png
Normal file
BIN
images/icon_x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
images/icon_x512.png
Normal file
BIN
images/icon_x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
images/maskable_icon_x192.png
Normal file
BIN
images/maskable_icon_x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5 KiB |
BIN
images/maskable_icon_x512.png
Normal file
BIN
images/maskable_icon_x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
40
manifest.json
Normal file
40
manifest.json
Normal 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
1667
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue