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 };
|