Initial commit
62
.eslintrc.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2019,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"svelte3"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*.svelte"
|
||||||
|
],
|
||||||
|
"processor": "svelte3/svelte3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"arrow-spacing": "error",
|
||||||
|
"block-scoped-var": "error",
|
||||||
|
"block-spacing": "error",
|
||||||
|
"brace-style": ["error", "stroustrup", {}],
|
||||||
|
"camelcase": "error",
|
||||||
|
"comma-dangle": ["error", "never"],
|
||||||
|
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||||
|
"comma-style": [1, "last"],
|
||||||
|
"consistent-this": [1, "_this"],
|
||||||
|
"curly": [1, "multi"],
|
||||||
|
"eol-last": 1,
|
||||||
|
"eqeqeq": 1,
|
||||||
|
"func-names": 1,
|
||||||
|
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||||
|
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||||
|
"max-len": [1, 240, 2], // 2 spaces per tab, max 80 chars per line
|
||||||
|
"new-cap": 1,
|
||||||
|
"newline-before-return": "error",
|
||||||
|
"no-array-constructor": 1,
|
||||||
|
"no-inner-declarations": [1, "both"],
|
||||||
|
"no-mixed-spaces-and-tabs": 1,
|
||||||
|
"no-multi-spaces": 2,
|
||||||
|
"no-new-object": 1,
|
||||||
|
"no-shadow-restricted-names": 1,
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-template": "error",
|
||||||
|
"one-var": 0,
|
||||||
|
"quote-props": ["error", "always"],
|
||||||
|
"quotes": [1, "single"],
|
||||||
|
"radix": 1,
|
||||||
|
"semi": [1, "always"],
|
||||||
|
"space-before-blocks": [1, "always"],
|
||||||
|
"space-infix-ops": 1,
|
||||||
|
"vars-on-top": 1,
|
||||||
|
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||||
|
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules/
|
||||||
|
/public/build/
|
||||||
|
|
||||||
|
.DS_Store
|
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
6
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
.idea/jsLibraryMappings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptLibraryMappings">
|
||||||
|
<includedPredefinedLibrary name="Node.js Core" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/jsLinters/eslint.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EslintConfiguration">
|
||||||
|
<option name="fix-on-save" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/misc.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/svelte-multiview.iml" filepath="$PROJECT_DIR$/.idea/svelte-multiview.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
12
.idea/svelte-multiview.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
106
README.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Updates
|
||||||
|
|
||||||
|
## 2020-04-17
|
||||||
|
|
||||||
|
Added [svelte-preprocess](https://www.npmjs.com/package/svelte-preprocess) preprocessor with support for: PostCSS, SCSS, Less, Stylus, Coffeescript, TypeScript and Pug.
|
||||||
|
|
||||||
|
Added [rollup-plugin-node-builtins](https://www.npmjs.com/package/rollup-plugin-node-builtins) Allows the node builtins to be required/imported. Doing so gives the proper shims to support modules that were designed for Browserify, some modules require rollup-plugin-node-globals.
|
||||||
|
|
||||||
|
Added [rollup-plugin-node-globals](https://www.npmjs.com/package/rollup-plugin-node-globals) Plugin to insert node globals including so code that works with browserify should work even if it uses process or buffers.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# svelte app
|
||||||
|
|
||||||
|
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||||
|
|
||||||
|
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx degit sveltejs/template svelte-app
|
||||||
|
cd svelte-app
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||||
|
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
Install the dependencies...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd svelte-app
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
...then start [Rollup](https://rollupjs.org):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||||
|
|
||||||
|
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
||||||
|
|
||||||
|
|
||||||
|
## Building and running in production mode
|
||||||
|
|
||||||
|
To create an optimised version of the app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||||
|
|
||||||
|
|
||||||
|
## Single-page app mode
|
||||||
|
|
||||||
|
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||||
|
|
||||||
|
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||||
|
|
||||||
|
```js
|
||||||
|
"start": "sirv public --single"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Deploying to the web
|
||||||
|
|
||||||
|
### With [now](https://zeit.co/now)
|
||||||
|
|
||||||
|
Install `now` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g now
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd public
|
||||||
|
now deploy --name my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
||||||
|
|
||||||
|
### With [surge](https://surge.sh/)
|
||||||
|
|
||||||
|
Install `surge` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g surge
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
surge public my-project.surge.sh
|
||||||
|
```
|
7
ecosystem.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
'apps' : [{
|
||||||
|
'name': 'Multiview',
|
||||||
|
'script': './server.js',
|
||||||
|
'watch': './server.js'
|
||||||
|
}]
|
||||||
|
};
|
4210
package-lock.json
generated
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte-app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c",
|
||||||
|
"dev": "rollup -c -w",
|
||||||
|
"start": "sirv public"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^11.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^7.0.0",
|
||||||
|
"autoprefixer": "^9.8.0",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-svelte3": "^2.7.3",
|
||||||
|
"rollup": "^1.20.0",
|
||||||
|
"rollup-plugin-livereload": "^1.0.0",
|
||||||
|
"rollup-plugin-node-builtins": "^2.1.2",
|
||||||
|
"rollup-plugin-node-globals": "^1.4.0",
|
||||||
|
"rollup-plugin-svelte": "^5.0.3",
|
||||||
|
"rollup-plugin-terser": "^5.1.2",
|
||||||
|
"svelte": "^3.0.0",
|
||||||
|
"svelte-preprocess": "^3.7.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"node-sass": "^4.14.1",
|
||||||
|
"polka": "^0.5.2",
|
||||||
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
|
"sirv": "^0.4.6",
|
||||||
|
"sirv-cli": "^0.4.4",
|
||||||
|
"video.js": "^7.8.2",
|
||||||
|
"videojs-youtube": "^2.6.1"
|
||||||
|
}
|
||||||
|
}
|
9
public/browserconfig.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/img/mstile-150x150.png"/>
|
||||||
|
<TileColor>#da532c</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
public/favicon.png
Normal file
After Width: | Height: | Size: 25 KiB |
79
public/global.css
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
html, body {
|
||||||
|
background-color: #000000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Muli', sans-serif;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 1px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quarter {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
border: 3px solid #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
padding-bottom: 56.55%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 3px solid #ff3333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background-color: #000000;
|
||||||
|
opacity: 0.3;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
padding: 0px 8px 5px 5px;
|
||||||
|
color: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.live .overlay {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline {
|
||||||
|
background-color: #111111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offline p {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
top: 25%;
|
||||||
|
left: 25%;
|
||||||
|
width: 50%;
|
||||||
|
height: 50%;
|
||||||
|
|
||||||
|
}
|
BIN
public/img/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/img/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
public/img/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
public/img/favicon-16x16.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
public/img/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/img/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/img/mstile-150x150.png
Normal file
After Width: | Height: | Size: 435 B |
1
public/img/safari-pinned-tab.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="1706.667" height="1706.667" viewBox="0 0 1280.000000 1280.000000"><path d="M0 182.8v182.9l182.8-.1 182.7-.1.1-182.8.1-182.7H0v182.8zM457 182.8v182.9h366V0H457v182.8zM914.4 182.7l.1 182.8 182.8.1 182.7.1V0H914.3l.1 182.7zM0 640v183l182.8-.2 182.7-.3.1-182.7.1-182.8H0v183zM457 640v183h366V457H457v183zM914.7 457.7c-.2.5-.4 82.8-.4 183V823H1280V457h-182.4c-100.4 0-182.7.3-182.9.7zM0 1097.2V1280H365.7l-.1-182.8-.1-182.7-182.7-.1L0 914.3v182.9zM457 1097.2V1280h366l-.2-182.8-.3-182.7-182.7-.1-182.8-.1v182.9zM914.3 1097.2V1280H1280V914.3H914.3v182.9z"/></svg>
|
After Width: | Height: | Size: 616 B |
36
public/index.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
|
<title>Multiview</title>
|
||||||
|
|
||||||
|
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="/img/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
|
<link rel="shortcut icon" href="/img/favicon.ico">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Multiview">
|
||||||
|
<meta name="application-name" content="Multiview">
|
||||||
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
|
<meta name="msapplication-config" content="/img/browserconfig.xml">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
<!--<link href="//vjs.zencdn.net/7.3.0/video-js.min.css" rel="stylesheet">-->
|
||||||
|
<script src="//vjs.zencdn.net/7.3.0/video.min.js"></script>
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Muli" rel="stylesheet">
|
||||||
|
<link rel='stylesheet' href='/global.css'>
|
||||||
|
<link rel='stylesheet' href='/build/bundle.css'>
|
||||||
|
|
||||||
|
<script src="//www.youtube.com/iframe_api"></script>
|
||||||
|
<script src= "//player.twitch.tv/js/embed/v1.js"></script>
|
||||||
|
|
||||||
|
<script defer src='/build/bundle.js'></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
544
public/js/videojs.youtube.min.js
vendored
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
(function (root, factory) {
|
||||||
|
if (typeof exports === "object" && typeof module !== "undefined") {
|
||||||
|
var videojs = require("video.js");
|
||||||
|
module.exports = factory(videojs.default || videojs)
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
define(["videojs"], function (videojs) {
|
||||||
|
return root.Youtube = factory(videojs)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
root.Youtube = factory(root.videojs)
|
||||||
|
}
|
||||||
|
})(this, function (videojs) {
|
||||||
|
"use strict";
|
||||||
|
var _isOnMobile = videojs.browser.IS_IOS || videojs.browser.IS_NATIVE_ANDROID;
|
||||||
|
var Tech = videojs.getTech("Tech");
|
||||||
|
var Youtube = videojs.extend(Tech, {
|
||||||
|
constructor: function (options, ready) {
|
||||||
|
Tech.call(this, options, ready);
|
||||||
|
this.setPoster(options.poster);
|
||||||
|
this.setSrc(this.options_.source, true);
|
||||||
|
this.setTimeout(function () {
|
||||||
|
if (this.el_) {
|
||||||
|
this.el_.parentNode.className += " vjs-youtube";
|
||||||
|
if (_isOnMobile) {
|
||||||
|
this.el_.parentNode.className += " vjs-youtube-mobile"
|
||||||
|
}
|
||||||
|
if (Youtube.isApiReady) {
|
||||||
|
this.initYTPlayer()
|
||||||
|
} else {
|
||||||
|
Youtube.apiReadyQueue.push(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}, dispose: function () {
|
||||||
|
if (this.ytPlayer) {
|
||||||
|
if (this.ytPlayer.stopVideo) {
|
||||||
|
this.ytPlayer.stopVideo()
|
||||||
|
}
|
||||||
|
if (this.ytPlayer.destroy) {
|
||||||
|
this.ytPlayer.destroy()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var index = Youtube.apiReadyQueue.indexOf(this);
|
||||||
|
if (index !== -1) {
|
||||||
|
Youtube.apiReadyQueue.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ytPlayer = null;
|
||||||
|
this.el_.parentNode.className = this.el_.parentNode.className.replace(" vjs-youtube", "").replace(" vjs-youtube-mobile", "");
|
||||||
|
this.el_.parentNode.removeChild(this.el_);
|
||||||
|
Tech.prototype.dispose.call(this)
|
||||||
|
}, createEl: function () {
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.setAttribute("id", this.options_.techId);
|
||||||
|
div.setAttribute("style", "width:100%;height:100%;top:0;left:0;position:absolute");
|
||||||
|
div.setAttribute("class", "vjs-tech");
|
||||||
|
var divWrapper = document.createElement("div");
|
||||||
|
divWrapper.appendChild(div);
|
||||||
|
if (!_isOnMobile && !this.options_.ytControls) {
|
||||||
|
var divBlocker = document.createElement("div");
|
||||||
|
divBlocker.setAttribute("class", "vjs-iframe-blocker");
|
||||||
|
divBlocker.setAttribute("style", "position:absolute;top:0;left:0;width:100%;height:100%");
|
||||||
|
divBlocker.onclick = function () {
|
||||||
|
this.pause()
|
||||||
|
}.bind(this);
|
||||||
|
divWrapper.appendChild(divBlocker)
|
||||||
|
}
|
||||||
|
return divWrapper
|
||||||
|
}, initYTPlayer: function () {
|
||||||
|
var playerVars = {controls: 0, modestbranding: 1, rel: 0, showinfo: 0, loop: this.options_.loop ? 1 : 0};
|
||||||
|
if (typeof this.options_.autohide !== "undefined") {
|
||||||
|
playerVars.autohide = this.options_.autohide
|
||||||
|
}
|
||||||
|
if (typeof this.options_["cc_load_policy"] !== "undefined") {
|
||||||
|
playerVars["cc_load_policy"] = this.options_["cc_load_policy"]
|
||||||
|
}
|
||||||
|
if (typeof this.options_.ytControls !== "undefined") {
|
||||||
|
playerVars.controls = this.options_.ytControls
|
||||||
|
}
|
||||||
|
if (typeof this.options_.disablekb !== "undefined") {
|
||||||
|
playerVars.disablekb = this.options_.disablekb
|
||||||
|
}
|
||||||
|
if (typeof this.options_.color !== "undefined") {
|
||||||
|
playerVars.color = this.options_.color
|
||||||
|
}
|
||||||
|
if (!playerVars.controls) {
|
||||||
|
playerVars.fs = 0
|
||||||
|
} else if (typeof this.options_.fs !== "undefined") {
|
||||||
|
playerVars.fs = this.options_.fs
|
||||||
|
}
|
||||||
|
if (this.options_.source.src.indexOf("end=") !== -1) {
|
||||||
|
var srcEndTime = this.options_.source.src.match(/end=([0-9]*)/);
|
||||||
|
this.options_.end = parseInt(srcEndTime[1])
|
||||||
|
}
|
||||||
|
if (typeof this.options_.end !== "undefined") {
|
||||||
|
playerVars.end = this.options_.end
|
||||||
|
}
|
||||||
|
if (typeof this.options_.hl !== "undefined") {
|
||||||
|
playerVars.hl = this.options_.hl
|
||||||
|
} else if (typeof this.options_.language !== "undefined") {
|
||||||
|
playerVars.hl = this.options_.language.substr(0, 2)
|
||||||
|
}
|
||||||
|
if (typeof this.options_["iv_load_policy"] !== "undefined") {
|
||||||
|
playerVars["iv_load_policy"] = this.options_["iv_load_policy"]
|
||||||
|
}
|
||||||
|
if (typeof this.options_.list !== "undefined") {
|
||||||
|
playerVars.list = this.options_.list
|
||||||
|
} else if (this.url && typeof this.url.listId !== "undefined") {
|
||||||
|
playerVars.list = this.url.listId
|
||||||
|
}
|
||||||
|
if (typeof this.options_.listType !== "undefined") {
|
||||||
|
playerVars.listType = this.options_.listType
|
||||||
|
}
|
||||||
|
if (typeof this.options_.modestbranding !== "undefined") {
|
||||||
|
playerVars.modestbranding = this.options_.modestbranding
|
||||||
|
}
|
||||||
|
if (typeof this.options_.playlist !== "undefined") {
|
||||||
|
playerVars.playlist = this.options_.playlist
|
||||||
|
}
|
||||||
|
if (typeof this.options_.playsinline !== "undefined") {
|
||||||
|
playerVars.playsinline = this.options_.playsinline
|
||||||
|
}
|
||||||
|
if (typeof this.options_.rel !== "undefined") {
|
||||||
|
playerVars.rel = this.options_.rel
|
||||||
|
}
|
||||||
|
if (typeof this.options_.showinfo !== "undefined") {
|
||||||
|
playerVars.showinfo = this.options_.showinfo
|
||||||
|
}
|
||||||
|
if (this.options_.source.src.indexOf("start=") !== -1) {
|
||||||
|
var srcStartTime = this.options_.source.src.match(/start=([0-9]*)/);
|
||||||
|
this.options_.start = parseInt(srcStartTime[1])
|
||||||
|
}
|
||||||
|
if (typeof this.options_.start !== "undefined") {
|
||||||
|
playerVars.start = this.options_.start
|
||||||
|
}
|
||||||
|
if (typeof this.options_.theme !== "undefined") {
|
||||||
|
playerVars.theme = this.options_.theme
|
||||||
|
}
|
||||||
|
if (typeof this.options_.customVars !== "undefined") {
|
||||||
|
var customVars = this.options_.customVars;
|
||||||
|
Object.keys(customVars).forEach(function (key) {
|
||||||
|
playerVars[key] = customVars[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.activeVideoId = this.url ? this.url.videoId : null;
|
||||||
|
this.activeList = playerVars.list;
|
||||||
|
var playerConfig = {
|
||||||
|
videoId: this.activeVideoId,
|
||||||
|
playerVars: playerVars,
|
||||||
|
events: {
|
||||||
|
onReady: this.onPlayerReady.bind(this),
|
||||||
|
onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this),
|
||||||
|
onPlaybackRateChange: this.onPlayerPlaybackRateChange.bind(this),
|
||||||
|
onStateChange: this.onPlayerStateChange.bind(this),
|
||||||
|
onVolumeChange: this.onPlayerVolumeChange.bind(this),
|
||||||
|
onError: this.onPlayerError.bind(this)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof this.options_.enablePrivacyEnhancedMode !== "undefined" && this.options_.enablePrivacyEnhancedMode) {
|
||||||
|
playerConfig.host = "https://www.youtube-nocookie.com"
|
||||||
|
}
|
||||||
|
this.ytPlayer = new YT.Player(this.options_.techId, playerConfig)
|
||||||
|
}, onPlayerReady: function () {
|
||||||
|
if (this.options_.muted) {
|
||||||
|
this.ytPlayer.mute()
|
||||||
|
}
|
||||||
|
var playbackRates = this.ytPlayer.getAvailablePlaybackRates();
|
||||||
|
if (playbackRates.length > 1) {
|
||||||
|
this.featuresPlaybackRate = true
|
||||||
|
}
|
||||||
|
this.playerReady_ = true;
|
||||||
|
this.triggerReady();
|
||||||
|
if (this.playOnReady) {
|
||||||
|
this.play()
|
||||||
|
} else if (this.cueOnReady) {
|
||||||
|
this.cueVideoById_(this.url.videoId);
|
||||||
|
this.activeVideoId = this.url.videoId
|
||||||
|
}
|
||||||
|
}, onPlayerPlaybackQualityChange: function () {
|
||||||
|
}, onPlayerPlaybackRateChange: function () {
|
||||||
|
this.trigger("ratechange")
|
||||||
|
}, onPlayerStateChange: function (e) {
|
||||||
|
var state = e.data;
|
||||||
|
if (state === this.lastState || this.errorNumber) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.lastState = state;
|
||||||
|
switch (state) {
|
||||||
|
case-1:
|
||||||
|
this.trigger("loadstart");
|
||||||
|
this.trigger("loadedmetadata");
|
||||||
|
this.trigger("durationchange");
|
||||||
|
this.trigger("ratechange");
|
||||||
|
break;
|
||||||
|
case YT.PlayerState.ENDED:
|
||||||
|
this.trigger("ended");
|
||||||
|
break;
|
||||||
|
case YT.PlayerState.PLAYING:
|
||||||
|
this.trigger("timeupdate");
|
||||||
|
this.trigger("durationchange");
|
||||||
|
this.trigger("playing");
|
||||||
|
this.trigger("play");
|
||||||
|
if (this.isSeeking) {
|
||||||
|
this.onSeeked()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case YT.PlayerState.PAUSED:
|
||||||
|
this.trigger("canplay");
|
||||||
|
if (this.isSeeking) {
|
||||||
|
this.onSeeked()
|
||||||
|
} else {
|
||||||
|
this.trigger("pause")
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case YT.PlayerState.BUFFERING:
|
||||||
|
this.player_.trigger("timeupdate");
|
||||||
|
this.player_.trigger("waiting");
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, onPlayerVolumeChange: function () {
|
||||||
|
this.trigger("volumechange")
|
||||||
|
}, onPlayerError: function (e) {
|
||||||
|
this.errorNumber = e.data;
|
||||||
|
this.trigger("pause");
|
||||||
|
this.trigger("error")
|
||||||
|
}, error: function () {
|
||||||
|
var code = 1e3 + this.errorNumber;
|
||||||
|
switch (this.errorNumber) {
|
||||||
|
case 5:
|
||||||
|
return {code: code, message: "Error while trying to play the video"};
|
||||||
|
case 2:
|
||||||
|
case 100:
|
||||||
|
return {code: code, message: "Unable to find the video"};
|
||||||
|
case 101:
|
||||||
|
case 150:
|
||||||
|
return {code: code, message: "Playback on other Websites has been disabled by the video owner."}
|
||||||
|
}
|
||||||
|
return {code: code, message: "YouTube unknown error (" + this.errorNumber + ")"}
|
||||||
|
}, loadVideoById_: function (id) {
|
||||||
|
var options = {videoId: id};
|
||||||
|
if (this.options_.start) {
|
||||||
|
options.startSeconds = this.options_.start
|
||||||
|
}
|
||||||
|
if (this.options_.end) {
|
||||||
|
options.endEnd = this.options_.end
|
||||||
|
}
|
||||||
|
this.ytPlayer.loadVideoById(options)
|
||||||
|
}, cueVideoById_: function (id) {
|
||||||
|
var options = {videoId: id};
|
||||||
|
if (this.options_.start) {
|
||||||
|
options.startSeconds = this.options_.start
|
||||||
|
}
|
||||||
|
if (this.options_.end) {
|
||||||
|
options.endEnd = this.options_.end
|
||||||
|
}
|
||||||
|
this.ytPlayer.cueVideoById(options)
|
||||||
|
}, src: function (src) {
|
||||||
|
if (src) {
|
||||||
|
this.setSrc({src: src})
|
||||||
|
}
|
||||||
|
return this.source
|
||||||
|
}, poster: function () {
|
||||||
|
if (_isOnMobile) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.poster_
|
||||||
|
}, setPoster: function (poster) {
|
||||||
|
this.poster_ = poster
|
||||||
|
}, setSrc: function (source) {
|
||||||
|
if (!source || !source.src) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete this.errorNumber;
|
||||||
|
this.source = source;
|
||||||
|
this.url = Youtube.parseUrl(source.src);
|
||||||
|
if (!this.options_.poster) {
|
||||||
|
if (this.url.videoId) {
|
||||||
|
this.poster_ = "https://img.youtube.com/vi/" + this.url.videoId + "/0.jpg";
|
||||||
|
this.trigger("posterchange");
|
||||||
|
this.checkHighResPoster()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.options_.autoplay && !_isOnMobile) {
|
||||||
|
if (this.isReady_) {
|
||||||
|
this.play()
|
||||||
|
} else {
|
||||||
|
this.playOnReady = true
|
||||||
|
}
|
||||||
|
} else if (this.activeVideoId !== this.url.videoId) {
|
||||||
|
if (this.isReady_) {
|
||||||
|
this.cueVideoById_(this.url.videoId);
|
||||||
|
this.activeVideoId = this.url.videoId
|
||||||
|
} else {
|
||||||
|
this.cueOnReady = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, autoplay: function () {
|
||||||
|
return this.options_.autoplay
|
||||||
|
}, setAutoplay: function (val) {
|
||||||
|
this.options_.autoplay = val
|
||||||
|
}, loop: function () {
|
||||||
|
return this.options_.loop
|
||||||
|
}, setLoop: function (val) {
|
||||||
|
this.options_.loop = val
|
||||||
|
}, play: function () {
|
||||||
|
if (!this.url || !this.url.videoId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.wasPausedBeforeSeek = false;
|
||||||
|
if (this.isReady_) {
|
||||||
|
if (this.url.listId) {
|
||||||
|
if (this.activeList === this.url.listId) {
|
||||||
|
this.ytPlayer.playVideo()
|
||||||
|
} else {
|
||||||
|
this.ytPlayer.loadPlaylist(this.url.listId);
|
||||||
|
this.activeList = this.url.listId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.activeVideoId === this.url.videoId) {
|
||||||
|
this.ytPlayer.playVideo()
|
||||||
|
} else {
|
||||||
|
this.loadVideoById_(this.url.videoId);
|
||||||
|
this.activeVideoId = this.url.videoId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.trigger("waiting");
|
||||||
|
this.playOnReady = true
|
||||||
|
}
|
||||||
|
}, pause: function () {
|
||||||
|
if (this.ytPlayer) {
|
||||||
|
this.ytPlayer.pauseVideo()
|
||||||
|
}
|
||||||
|
}, paused: function () {
|
||||||
|
return this.ytPlayer ? this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING : true
|
||||||
|
}, currentTime: function () {
|
||||||
|
return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0
|
||||||
|
}, setCurrentTime: function (seconds) {
|
||||||
|
if (this.lastState === YT.PlayerState.PAUSED) {
|
||||||
|
this.timeBeforeSeek = this.currentTime()
|
||||||
|
}
|
||||||
|
if (!this.isSeeking) {
|
||||||
|
this.wasPausedBeforeSeek = this.paused()
|
||||||
|
}
|
||||||
|
this.ytPlayer.seekTo(seconds, true);
|
||||||
|
this.trigger("timeupdate");
|
||||||
|
this.trigger("seeking");
|
||||||
|
this.isSeeking = true;
|
||||||
|
if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) {
|
||||||
|
clearInterval(this.checkSeekedInPauseInterval);
|
||||||
|
this.checkSeekedInPauseInterval = setInterval(function () {
|
||||||
|
if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) {
|
||||||
|
clearInterval(this.checkSeekedInPauseInterval)
|
||||||
|
} else if (this.currentTime() !== this.timeBeforeSeek) {
|
||||||
|
this.trigger("timeupdate");
|
||||||
|
this.onSeeked()
|
||||||
|
}
|
||||||
|
}.bind(this), 250)
|
||||||
|
}
|
||||||
|
}, seeking: function () {
|
||||||
|
return this.isSeeking
|
||||||
|
}, seekable: function () {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return videojs.createTimeRange()
|
||||||
|
}
|
||||||
|
return videojs.createTimeRange(0, this.ytPlayer.getDuration())
|
||||||
|
}, onSeeked: function () {
|
||||||
|
clearInterval(this.checkSeekedInPauseInterval);
|
||||||
|
this.isSeeking = false;
|
||||||
|
if (this.wasPausedBeforeSeek) {
|
||||||
|
this.pause()
|
||||||
|
}
|
||||||
|
this.trigger("seeked")
|
||||||
|
}, playbackRate: function () {
|
||||||
|
return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1
|
||||||
|
}, setPlaybackRate: function (suggestedRate) {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ytPlayer.setPlaybackRate(suggestedRate)
|
||||||
|
}, duration: function () {
|
||||||
|
return this.ytPlayer ? this.ytPlayer.getDuration() : 0
|
||||||
|
}, currentSrc: function () {
|
||||||
|
return this.source && this.source.src
|
||||||
|
}, ended: function () {
|
||||||
|
return this.ytPlayer ? this.lastState === YT.PlayerState.ENDED : false
|
||||||
|
}, volume: function () {
|
||||||
|
return this.ytPlayer ? this.ytPlayer.getVolume() / 100 : 1
|
||||||
|
}, setVolume: function (percentAsDecimal) {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.ytPlayer.setVolume(percentAsDecimal * 100)
|
||||||
|
}, muted: function () {
|
||||||
|
return this.ytPlayer ? this.ytPlayer.isMuted() : false
|
||||||
|
}, setMuted: function (mute) {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
this.muted(true)
|
||||||
|
}
|
||||||
|
if (mute) {
|
||||||
|
this.ytPlayer.mute()
|
||||||
|
} else {
|
||||||
|
this.ytPlayer.unMute()
|
||||||
|
}
|
||||||
|
this.setTimeout(function () {
|
||||||
|
this.trigger("volumechange")
|
||||||
|
}, 50)
|
||||||
|
}, buffered: function () {
|
||||||
|
if (!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) {
|
||||||
|
return videojs.createTimeRange()
|
||||||
|
}
|
||||||
|
var bufferedEnd = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration();
|
||||||
|
return videojs.createTimeRange(0, bufferedEnd)
|
||||||
|
}, preload: function () {
|
||||||
|
}, load: function () {
|
||||||
|
}, reset: function () {
|
||||||
|
}, networkState: function () {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch (this.ytPlayer.getPlayerState()) {
|
||||||
|
case-1:
|
||||||
|
return 0;
|
||||||
|
case 3:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}, readyState: function () {
|
||||||
|
if (!this.ytPlayer) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch (this.ytPlayer.getPlayerState()) {
|
||||||
|
case-1:
|
||||||
|
return 0;
|
||||||
|
case 5:
|
||||||
|
return 1;
|
||||||
|
case 3:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}, supportsFullScreen: function () {
|
||||||
|
return document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled
|
||||||
|
}, checkHighResPoster: function () {
|
||||||
|
var uri = "https://img.youtube.com/vi/" + this.url.videoId + "/maxresdefault.jpg";
|
||||||
|
try {
|
||||||
|
var image = new Image;
|
||||||
|
image.onload = function () {
|
||||||
|
if ("naturalHeight" in image) {
|
||||||
|
if (image.naturalHeight <= 90 || image.naturalWidth <= 120) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (image.height <= 90 || image.width <= 120) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.poster_ = uri;
|
||||||
|
this.trigger("posterchange")
|
||||||
|
}.bind(this);
|
||||||
|
image.onerror = function () {
|
||||||
|
};
|
||||||
|
image.src = uri
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Youtube.isSupported = function () {
|
||||||
|
return true
|
||||||
|
};
|
||||||
|
Youtube.canPlaySource = function (e) {
|
||||||
|
return Youtube.canPlayType(e.type)
|
||||||
|
};
|
||||||
|
Youtube.canPlayType = function (e) {
|
||||||
|
return e === "video/youtube"
|
||||||
|
};
|
||||||
|
Youtube.parseUrl = function (url) {
|
||||||
|
var result = {videoId: null};
|
||||||
|
var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
||||||
|
var match = url.match(regex);
|
||||||
|
if (match && match[2].length === 11) {
|
||||||
|
result.videoId = match[2]
|
||||||
|
}
|
||||||
|
var regPlaylist = /[?&]list=([^#\&\?]+)/;
|
||||||
|
match = url.match(regPlaylist);
|
||||||
|
if (match && match[1]) {
|
||||||
|
result.listId = match[1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
};
|
||||||
|
|
||||||
|
function apiLoaded() {
|
||||||
|
YT.ready(function () {
|
||||||
|
Youtube.isApiReady = true;
|
||||||
|
for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) {
|
||||||
|
Youtube.apiReadyQueue[i].initYTPlayer()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadScript(src, callback) {
|
||||||
|
var loaded = false;
|
||||||
|
var tag = document.createElement("script");
|
||||||
|
var firstScriptTag = document.getElementsByTagName("script")[0];
|
||||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||||
|
tag.onload = function () {
|
||||||
|
if (!loaded) {
|
||||||
|
loaded = true;
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tag.onreadystatechange = function () {
|
||||||
|
if (!loaded && (this.readyState === "complete" || this.readyState === "loaded")) {
|
||||||
|
loaded = true;
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tag.src = src
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectCss() {
|
||||||
|
var css = ".vjs-youtube .vjs-iframe-blocker { display: none; }" + ".vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }" + ".vjs-youtube .vjs-poster { background-size: cover; }" + ".vjs-youtube-mobile .vjs-big-play-button { display: none; }";
|
||||||
|
var head = document.head || document.getElementsByTagName("head")[0];
|
||||||
|
var style = document.createElement("style");
|
||||||
|
style.type = "text/css";
|
||||||
|
if (style.styleSheet) {
|
||||||
|
style.styleSheet.cssText = css
|
||||||
|
} else {
|
||||||
|
style.appendChild(document.createTextNode(css))
|
||||||
|
}
|
||||||
|
head.appendChild(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
Youtube.apiReadyQueue = [];
|
||||||
|
if (typeof document !== "undefined") {
|
||||||
|
loadScript("https://www.youtube.com/iframe_api", apiLoaded);
|
||||||
|
injectCss()
|
||||||
|
}
|
||||||
|
if (typeof videojs.registerTech !== "undefined") {
|
||||||
|
videojs.registerTech("Youtube", Youtube)
|
||||||
|
} else {
|
||||||
|
videojs.registerComponent("Youtube", Youtube)
|
||||||
|
}
|
||||||
|
});
|
106
public/service-worker.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2016 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
const CACHE_VERSION = 8;
|
||||||
|
const dataCacheName = `multiview-v${CACHE_VERSION}`;
|
||||||
|
const cacheName = `multiview-final-${CACHE_VERSION}`;
|
||||||
|
const filesToCache = [
|
||||||
|
'/',
|
||||||
|
'/index.html',
|
||||||
|
'/service-worker.js',
|
||||||
|
'/site.webmanifest',
|
||||||
|
'/favicon.png',
|
||||||
|
'/browserconfig.xml',
|
||||||
|
'/build/bundle.css',
|
||||||
|
'/build/bundle.js',
|
||||||
|
'/img/android-chrome-192x192.png',
|
||||||
|
'/img/android-chrome-512x512.png',
|
||||||
|
'/img/favicon.ico',
|
||||||
|
'/img/favicon-16x16.png',
|
||||||
|
'/img/favicon-32x32.png'
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener('install', function(e) {
|
||||||
|
console.log('[ServiceWorker] Install');
|
||||||
|
e.waitUntil(
|
||||||
|
caches.open(cacheName).then(function(cache) {
|
||||||
|
console.log('[ServiceWorker] Caching app shell');
|
||||||
|
|
||||||
|
return cache.addAll(filesToCache);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', function(e) {
|
||||||
|
console.log('[ServiceWorker] Activate');
|
||||||
|
e.waitUntil(
|
||||||
|
caches.keys().then(function(keyList) {
|
||||||
|
return Promise.all(keyList.map(function(key) {
|
||||||
|
if (key !== cacheName && key !== dataCacheName) {
|
||||||
|
console.log('[ServiceWorker] Removing old cache', key);
|
||||||
|
|
||||||
|
return caches.delete(key);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fixes a corner case in which the app wasn't returning the latest data.
|
||||||
|
* You can reproduce the corner case by commenting out the line below and
|
||||||
|
* then doing the following steps: 1) load app for first time so that the
|
||||||
|
* initial New York City data is shown 2) press the refresh button on the
|
||||||
|
* app 3) go offline 4) reload the app. You expect to see the newer NYC
|
||||||
|
* data, but you actually see the initial data. This happens because the
|
||||||
|
* service worker is not yet activated. The code below essentially lets
|
||||||
|
* you activate the service worker faster.
|
||||||
|
*/
|
||||||
|
return self.clients.claim();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function(e) {
|
||||||
|
console.warn('[Service Worker] Fetch', e.request.url);
|
||||||
|
const dataUrl = '/getnexttraintimes?';
|
||||||
|
if (e.request.url.indexOf(dataUrl) > -1) {
|
||||||
|
console.log('!');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When the request URL contains dataUrl, the app is asking for fresh
|
||||||
|
* weather data. In this case, the service worker always goes to the
|
||||||
|
* network and then caches the response. This is called the "Cache then
|
||||||
|
* network" strategy:
|
||||||
|
* https://jakearchibald.com/2014/offline-cookbook/#cache-then-network
|
||||||
|
*/
|
||||||
|
e.respondWith(
|
||||||
|
caches.open(dataCacheName).then(function(cache) {
|
||||||
|
return fetch(e.request).then(function(response) {
|
||||||
|
cache.put(e.request.url, response.clone());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The app is asking for app shell files. In this scenario the app uses the
|
||||||
|
* "Cache, falling back to the network" offline strategy:
|
||||||
|
* https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network
|
||||||
|
*/
|
||||||
|
e.respondWith(
|
||||||
|
caches.match(e.request).then(function(response) {
|
||||||
|
return response || fetch(e.request);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
21
public/site.webmanifest
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "Multiview",
|
||||||
|
"short_name": "Multiview",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/img/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/img/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"start_url": ".",
|
||||||
|
"imgdisplay": "standalone",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
91
rollup.config.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import svelte from 'rollup-plugin-svelte';
|
||||||
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import livereload from 'rollup-plugin-livereload';
|
||||||
|
import replace from 'rollup-plugin-replace';
|
||||||
|
import { terser } from 'rollup-plugin-terser';
|
||||||
|
import sveltePreprocess from 'svelte-preprocess';
|
||||||
|
import builtins from 'rollup-plugin-node-builtins';
|
||||||
|
// import globals from 'rollup-plugin-node-globals';
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
const preprocess = sveltePreprocess({
|
||||||
|
'scss': {
|
||||||
|
'includePaths': ['src']
|
||||||
|
},
|
||||||
|
'postcss': {
|
||||||
|
'plugins': [require('autoprefixer')]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
'input': 'src/main.js',
|
||||||
|
'output': {
|
||||||
|
'sourcemap': (!production),
|
||||||
|
'format': 'iife',
|
||||||
|
'name': 'app',
|
||||||
|
'file': 'public/build/bundle.js'
|
||||||
|
},
|
||||||
|
'plugins': [
|
||||||
|
/*globals(),*/
|
||||||
|
builtins(),
|
||||||
|
svelte({
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
'dev': !production,
|
||||||
|
preprocess,
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file - better for performance
|
||||||
|
'css': css => {
|
||||||
|
css.write('public/build/bundle.css');
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration -
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
resolve({
|
||||||
|
'browser': true,
|
||||||
|
'dedupe': ['svelte']
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
replace({
|
||||||
|
'exclude': 'node_modules/**',
|
||||||
|
'ENV': JSON.stringify(production ? 'production' : 'development')
|
||||||
|
}),
|
||||||
|
|
||||||
|
// In dev mode, call `npm run start` once
|
||||||
|
// the bundle has been generated
|
||||||
|
!production && serve(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload('public'),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser()
|
||||||
|
],
|
||||||
|
'watch': {
|
||||||
|
'clearScreen': false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function serve() {
|
||||||
|
let started = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
writeBundle() {
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
|
||||||
|
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||||
|
'stdio': ['ignore', 'inherit', 'inherit'],
|
||||||
|
'shell': true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
16
server.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const sirv = require('sirv');
|
||||||
|
const polka = require('polka');
|
||||||
|
const compress = require('compression')();
|
||||||
|
|
||||||
|
// Init `sirv` handler
|
||||||
|
const assets = sirv('public', {
|
||||||
|
'maxAge': 31536000, // 1Y
|
||||||
|
'immutable': true
|
||||||
|
});
|
||||||
|
|
||||||
|
polka()
|
||||||
|
.use(compress, assets)
|
||||||
|
.listen(8130, err => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log('> Running on localhost:8130');
|
||||||
|
});
|
26
src/App.svelte
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script>
|
||||||
|
import {Playing} from './store/state';
|
||||||
|
|
||||||
|
import Youtube from "./components/Youtube.svelte";
|
||||||
|
import Live from "./components/Live.svelte";
|
||||||
|
import Twitch from "./components/Twitch.svelte";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
Playing:{$Playing}
|
||||||
|
</div>
|
||||||
|
<div id="container">
|
||||||
|
<Youtube title="Sky News" id="skynews" src="https://www.youtube.com/embed/9Auq9mYxFEE?enablejsapi=1&autoplay=1&mute=1&controls=0&fs=0&modestbranding=1&cc_load_policy=1"/>
|
||||||
|
<Live title="BBC News" id="bbc" src="https://a.files.bbci.co.uk/media/live/manifesto/audio_video/simulcast/hls/uk/abr_hdtv/ak/bbc_news24.m3u8"/>
|
||||||
|
<Youtube title="EuroNews" id="euronews" src="https://www.youtube.com/embed/6xrJy-1_qS4?enablejsapi=1&autoplay=1&mute=1&controls=0&fs=0&modestbranding=1&cc_load_policy=1"/>
|
||||||
|
<Twitch title="twitch.tv/rukpolitics" id="rukpolitics" channel="rukpolitics"/>
|
||||||
|
<Twitch title="twitch.tv/ukcommons" id="ukcommons" channel="ukcommons"/>
|
||||||
|
<Twitch title="twitch.tv/democracylive" id="democracylive" channel="democracylive"/>
|
||||||
|
</div>
|
||||||
|
|
73
src/components/Live.svelte
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<script>
|
||||||
|
import {onMount} from 'svelte';
|
||||||
|
import {Playing, actions} from '../store/state';
|
||||||
|
import videojs from 'video.js';
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
export let src;
|
||||||
|
export let title;
|
||||||
|
let fullId = '';
|
||||||
|
let active = '';
|
||||||
|
const dataSetup = {"youtube": {"ytControls": 0}};
|
||||||
|
let player;
|
||||||
|
|
||||||
|
$: fullId = `${id}-live`;
|
||||||
|
|
||||||
|
function mute() {
|
||||||
|
console.log(`${fullId} - mute`);
|
||||||
|
player.muted(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function unMute() {
|
||||||
|
console.log(`${fullId} - unmute`);
|
||||||
|
player.muted(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
actions.setPlaying(fullId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
player = videojs(fullId);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Playing.subscribe((v) => {
|
||||||
|
active = (fullId !== '' && v === fullId) ? 'active' : '';
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
mute();
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
unMute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`mounted ${fullId} player`, player);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="quarter" on:click={handleClick}>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="stream live {active}">
|
||||||
|
<div class="overlay"></div>
|
||||||
|
<div class="title">{title}</div>
|
||||||
|
<video id="{fullId}" class="video-js vjs-16-9" autoplay muted preload="auto" data-setup='{JSON.stringify(dataSetup)}'>
|
||||||
|
<source src="{src}" type="application/x-mpegURL">
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
91
src/components/Twitch.svelte
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<script>
|
||||||
|
import {onMount} from 'svelte';
|
||||||
|
import {Playing, actions} from '../store/state';
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
export let channel;
|
||||||
|
export let title;
|
||||||
|
let fullId = '';
|
||||||
|
let active = '';
|
||||||
|
let player;
|
||||||
|
|
||||||
|
$: fullId = `${id}-twitch`;
|
||||||
|
|
||||||
|
function mute() {
|
||||||
|
console.log(`${fullId} - mute`);
|
||||||
|
player.setMuted(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function unMute() {
|
||||||
|
console.log(`${fullId} - unmute`);
|
||||||
|
player.setMuted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Playing.subscribe((v) => {
|
||||||
|
if (typeof(fullId) !== 'undefined') {
|
||||||
|
active = (fullId !== '' && v === fullId) ? 'active' : '';
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
mute();
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
unMute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
function handleClick() {
|
||||||
|
console.log(`click ${fullId}`);
|
||||||
|
actions.setPlaying(fullId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
player = new Twitch.Player(fullId, {
|
||||||
|
'channel': channel,
|
||||||
|
'muted': true,
|
||||||
|
'width': '100%',
|
||||||
|
'height': '100%'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mute();
|
||||||
|
|
||||||
|
Playing.subscribe((v) => {
|
||||||
|
active = (fullId !== '' && v === fullId) ? 'active' : '';
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
mute();
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
unMute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`mounted ${fullId} player`, player);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="quarter" on:click={handleClick}>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="stream live twitch {active}" data-video-id="4" id="{fullId}">
|
||||||
|
<div class="overlay"></div>
|
||||||
|
<div class="title">{title}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
96
src/components/Youtube.svelte
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<script>
|
||||||
|
import {onMount} from 'svelte';
|
||||||
|
import {Playing, actions} from '../store/state';
|
||||||
|
|
||||||
|
export let id;
|
||||||
|
export let src;
|
||||||
|
export let title;
|
||||||
|
let fullId;
|
||||||
|
let active = '';
|
||||||
|
let player;
|
||||||
|
|
||||||
|
$: fullId = `${id}-youtube`;
|
||||||
|
|
||||||
|
function mute() {
|
||||||
|
console.log(`${fullId} - mute`);
|
||||||
|
player.mute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function unMute() {
|
||||||
|
console.log(`${fullId} - unmute`);
|
||||||
|
player.unMute();
|
||||||
|
}
|
||||||
|
|
||||||
|
Playing.subscribe((v) => {
|
||||||
|
|
||||||
|
if (typeof(fullId) !== 'undefined') {
|
||||||
|
console.log(`${fullId} playing`, v);
|
||||||
|
active = (fullId !== '' && v === fullId) ? 'active' : '';
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
mute();
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
unMute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
actions.setPlaying(fullId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPlayer() {
|
||||||
|
console.log(`${fullId} createPlayer`);
|
||||||
|
try {
|
||||||
|
player = new YT.Player(fullId, {
|
||||||
|
'events': {
|
||||||
|
'onReady': function (event) {
|
||||||
|
console.log('READY!!');
|
||||||
|
event.target.mute();
|
||||||
|
// mute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`${fullId} Player`, player);
|
||||||
|
console.log('>>>');
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('---');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
console.log(`${fullId} videojs`, window);
|
||||||
|
|
||||||
|
setTimeout(async ()=> {
|
||||||
|
console.log('GO!');
|
||||||
|
await createPlayer()
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="quarter" on:click={handleClick}>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="stream live youtube {active}" data-youtube-id={fullId}>
|
||||||
|
<div class="overlay"></div>
|
||||||
|
<div class="title">Sky News</div>
|
||||||
|
<!-- <video id={fullId} class="video-js vjs-default-skin" controls>
|
||||||
|
<source src="{src}" type="video/youtube">
|
||||||
|
</video>-->
|
||||||
|
<iframe allow="autoplay" title="{title}" id={fullId} type="text/html" frameborder="0" width="100%" height="100%"
|
||||||
|
src="{src}"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
32
src/main.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import App from './App.svelte';
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
'target': document.body,
|
||||||
|
'props': {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
//
|
||||||
|
navigator.serviceWorker.ready.then(function(reg) {
|
||||||
|
console.warn('Ready??', reg);
|
||||||
|
// main();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register('./service-worker.js')
|
||||||
|
.then((r) => {
|
||||||
|
console.warn('Service Worker Registered', r.scope);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// registration failed
|
||||||
|
console.error(`Registration failed with ${ error}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
export default app;
|
24
src/store/state.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Created by WebStorm.
|
||||||
|
* User: martin
|
||||||
|
* Date: 27/05/2020
|
||||||
|
* Time: 10:04
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
const Playing = writable('');
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
setPlaying(id) {
|
||||||
|
console.log('>> setPlaying', id);
|
||||||
|
|
||||||
|
Playing.update((v) => {
|
||||||
|
return (v === id) ? '' : id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Playing, actions };
|