mirror of
https://github.com/jellyfin/jellyfin-tizen.git
synced 2024-11-23 05:49:52 +00:00
first commit
This commit is contained in:
commit
ea271573c1
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
*.wgt
|
||||||
|
www
|
||||||
|
.*
|
63
README.md
Normal file
63
README.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<h1 align="center">Jellyfin Tizen</h1>
|
||||||
|
<h3 align="center">Part of the <a href="https://jellyfin.media">Jellyfin Project</a></h3>
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
1. Download and install Tizen Studio (<a href="https://developer.tizen.org/development/tizen-studio/download">https://developer.tizen.org/development/tizen-studio/download</a>).
|
||||||
|
2. Setup Samsung certificate (need Samsung account).
|
||||||
|
3. Clone or download this repository.
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jellyfin/jellyfin-tizen.git
|
||||||
|
```
|
||||||
|
4. Clone or download Jellyfin Web repository.
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jellyfin/jellyfin-web.git
|
||||||
|
```
|
||||||
|
5. Go to Jellyfin Tizen directory.
|
||||||
|
```sh
|
||||||
|
cd jellyfin-tizen
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prepare Interface
|
||||||
|
|
||||||
|
If any changes are made to `jellyfin-web/`, the `www/` directory will need to be rebuilt using the following command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
JELLYFIN_WEB_DIR=../jellyfin-web/src npx gulp
|
||||||
|
```
|
||||||
|
|
||||||
|
> If `NODE_ENV=development` is set in the environment, then the source files will be copied without being minified.
|
||||||
|
|
||||||
|
> The `JELLYFIN_WEB_DIR` environment variable can be used to override the location of `jellyfin-web`.
|
||||||
|
|
||||||
|
### Build WGT
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tizen build-web -e ".*" -e gulpfile.js
|
||||||
|
tizen package -t wgt -o . -- .buildResult
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy to Emulator
|
||||||
|
|
||||||
|
1. Run emulator.
|
||||||
|
2. Install package.
|
||||||
|
```sh
|
||||||
|
tizen install -n *.wgt -t T-samsung-5.0-x86
|
||||||
|
```
|
||||||
|
> Specify target with `-t` option.
|
||||||
|
|
||||||
|
### Deploy to TV
|
||||||
|
|
||||||
|
1. Run TV.
|
||||||
|
2. Activate Developer Mode on TV (<a href="https://developer.samsung.com/tv/develop/getting-started/using-sdk/tv-device">https://developer.samsung.com/tv/develop/getting-started/using-sdk/tv-device</a>).
|
||||||
|
3. Connect to TV with Device Manager from Tizen Studio. Or with sdb.
|
||||||
|
```sh
|
||||||
|
sdb connect YOUR_TV_IP
|
||||||
|
```
|
||||||
|
4. Install package.
|
||||||
|
```sh
|
||||||
|
tizen install -n *.wgt -t UE65NU7400
|
||||||
|
```
|
||||||
|
> Specify target with `-t` option.
|
14
config.xml
Normal file
14
config.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<widget xmlns:tizen="http://tizen.org/ns/widgets" xmlns="http://www.w3.org/ns/widgets" id="http://jellyfin.org/Jellyfin" version="0.1.0" viewmodes="fullscreen">
|
||||||
|
<access origin="*" subdomains="true"></access>
|
||||||
|
<tizen:application id="AprZAARz4r.Jellyfin" package="AprZAARz4r" required_version="3.0"/>
|
||||||
|
<author href="http://jellyfin.org" email="apps@jellyfin.org">Jellyfin</author>
|
||||||
|
<content src="index.html"/>
|
||||||
|
<description>Jellyfin for Samsung Smart TV (Tizen).</description>
|
||||||
|
<feature name="http://tizen.org/feature/screen.size.all"/>
|
||||||
|
<icon src="icon.png"/>
|
||||||
|
<name>Jellyfin</name>
|
||||||
|
<tizen:privilege name="http://tizen.org/privilege/tv.inputdevice"/>
|
||||||
|
<tizen:profile name="tv"/>
|
||||||
|
<tizen:setting screen-orientation="auto-rotation" context-menu="enable" background-support="disable" encryption="disable" install-location="auto" hwkey-event="enable"/>
|
||||||
|
</widget>
|
100
gulpfile.js
Normal file
100
gulpfile.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
var gulp = require('gulp');
|
||||||
|
var gulpif = require('gulp-if');
|
||||||
|
var del = require('del');
|
||||||
|
var dom = require('gulp-dom');
|
||||||
|
var uglifyes = require('uglify-es');
|
||||||
|
var composer = require('gulp-uglify/composer');
|
||||||
|
var uglify = composer(uglifyes, console);
|
||||||
|
|
||||||
|
// Check the NODE_ENV environment variable
|
||||||
|
var isDev = process.env.NODE_ENV === 'development';
|
||||||
|
// Allow overriding of jellyfin-web directory
|
||||||
|
var WEB_DIR = process.env.JELLYFIN_WEB_DIR || 'node_modules/jellyfin-web/dist';
|
||||||
|
console.info('Using jellyfin-web from', WEB_DIR);
|
||||||
|
|
||||||
|
// Skip minification for development builds or minified files
|
||||||
|
var compress = !isDev && [
|
||||||
|
'**/*',
|
||||||
|
'!**/*min.*',
|
||||||
|
'!**/*hls.js',
|
||||||
|
// Temporarily exclude apiclient until updated
|
||||||
|
'!bower_components/emby-apiclient/**/*.js'
|
||||||
|
];
|
||||||
|
|
||||||
|
var uglifyOptions = {
|
||||||
|
compress: {
|
||||||
|
drop_console: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var paths = {
|
||||||
|
assets: {
|
||||||
|
src: [
|
||||||
|
WEB_DIR + '/**/*',
|
||||||
|
'!' + WEB_DIR + '/index.html'
|
||||||
|
],
|
||||||
|
dest: 'www/'
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
src: WEB_DIR + '/index.html',
|
||||||
|
dest: 'www/'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clean the www directory
|
||||||
|
function clean() {
|
||||||
|
return del([
|
||||||
|
'www'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy unmodified assets
|
||||||
|
function copy() {
|
||||||
|
return gulp.src(paths.assets.src)
|
||||||
|
.pipe(gulp.dest(paths.assets.dest));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add required tags to index.html
|
||||||
|
function modifyIndex() {
|
||||||
|
return gulp.src(paths.index.src)
|
||||||
|
.pipe(dom(function() {
|
||||||
|
// inject CSP meta tag
|
||||||
|
var meta = this.createElement('meta');
|
||||||
|
meta.setAttribute('http-equiv', 'Content-Security-Policy');
|
||||||
|
meta.setAttribute('content', 'default-src * \'self\' \'unsafe-inline\' \'unsafe-eval\' data: gap: file: filesystem: ws: wss:;');
|
||||||
|
this.head.appendChild(meta);
|
||||||
|
|
||||||
|
// inject appMode script
|
||||||
|
var appMode = this.createElement('script');
|
||||||
|
appMode.text = 'window.appMode=\'cordova\';';
|
||||||
|
this.body.appendChild(appMode);
|
||||||
|
|
||||||
|
// inject tizen.js
|
||||||
|
var tizen = this.createElement('script');
|
||||||
|
tizen.setAttribute('src', '../tizen.js');
|
||||||
|
tizen.setAttribute('defer', '');
|
||||||
|
this.body.appendChild(tizen);
|
||||||
|
|
||||||
|
// inject apploader.js
|
||||||
|
var apploader = this.createElement('script');
|
||||||
|
apploader.setAttribute('src', 'scripts/apploader.js');
|
||||||
|
apploader.setAttribute('defer', '');
|
||||||
|
this.body.appendChild(apploader);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest(paths.index.dest))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default build task
|
||||||
|
var build = gulp.series(
|
||||||
|
clean,
|
||||||
|
gulp.parallel(copy, modifyIndex)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Export tasks so they can be run individually
|
||||||
|
exports.clean = clean;
|
||||||
|
exports.copy = copy;
|
||||||
|
exports.modifyIndex = modifyIndex;
|
||||||
|
// Export default task
|
||||||
|
exports.default = build;
|
6
index.html
Normal file
6
index.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="refresh" content="0; url=www/index.html" />
|
||||||
|
</head>
|
||||||
|
</html>
|
179
tizen.js
Normal file
179
tizen.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
console.log('Tizen adapter');
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
|
||||||
|
//console.log(JSON.stringify(tizen.tvinputdevice.getSupportedKeys()));
|
||||||
|
|
||||||
|
tizen.tvinputdevice.registerKey('MediaPlay');
|
||||||
|
tizen.tvinputdevice.registerKey('MediaTrackPrevious');
|
||||||
|
tizen.tvinputdevice.registerKey('MediaTrackNext');
|
||||||
|
tizen.tvinputdevice.registerKey('MediaRewind');
|
||||||
|
tizen.tvinputdevice.registerKey('MediaFastForward');
|
||||||
|
|
||||||
|
require(['inputManager', 'focusManager', 'viewManager', 'appRouter', 'actionsheet'], function(inputManager, focusManager, viewManager, appRouter, actionsheet) {
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
'10009': 'back',
|
||||||
|
'415': 'playpause',
|
||||||
|
'10232': 'previoustrack',
|
||||||
|
'10233': 'nexttrack',
|
||||||
|
'412': 'rewind',
|
||||||
|
'417': 'fastforward'
|
||||||
|
};
|
||||||
|
|
||||||
|
var isRestored;
|
||||||
|
var lastActiveElement;
|
||||||
|
var historyStartup;
|
||||||
|
var historyDepth = 0;
|
||||||
|
var exitPromise;
|
||||||
|
|
||||||
|
//document.addEventListener('keypress', function(e) {
|
||||||
|
// console.log('keypress');
|
||||||
|
//});
|
||||||
|
|
||||||
|
//document.addEventListener('keyup', function(e) {
|
||||||
|
// console.log('keyup');
|
||||||
|
//});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
//console.log('keydown: keyCode: ' + e.keyCode + ' key: ' + e.key + ' location: ' + e.location);
|
||||||
|
|
||||||
|
var command = commands[e.keyCode];
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
//console.log('command: ' + command);
|
||||||
|
|
||||||
|
if (command === 'back' && historyDepth < 2 && !exitPromise) {
|
||||||
|
exitPromise = actionsheet.show({
|
||||||
|
title: Globalize.translate('Exit?'),
|
||||||
|
items: [
|
||||||
|
{id: 'yes', name: Globalize.translate('Yes')},
|
||||||
|
{id: 'no', name: Globalize.translate('No')}
|
||||||
|
]
|
||||||
|
}).then(function (value) {
|
||||||
|
exitPromise = null;
|
||||||
|
|
||||||
|
if (value === 'yes') {
|
||||||
|
try {
|
||||||
|
tizen.application.getCurrentApplication().exit();
|
||||||
|
} catch (ignore) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
exitPromise = null;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputManager.trigger(command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', function() {
|
||||||
|
lastActiveElement = document.activeElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('viewhide', function() {
|
||||||
|
lastActiveElement = document.activeElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
function onPageLoad() {
|
||||||
|
console.debug('onPageLoad ' + window.location.href + ' isRestored=' + isRestored);
|
||||||
|
|
||||||
|
if (isRestored) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = viewManager.currentView() || document.body;
|
||||||
|
|
||||||
|
var element = lastActiveElement;
|
||||||
|
lastActiveElement = null;
|
||||||
|
|
||||||
|
// These elements are recreated
|
||||||
|
if (element) {
|
||||||
|
if (element.classList.contains('btnPreviousPage')) {
|
||||||
|
element = view.querySelector('.btnPreviousPage');
|
||||||
|
} else if (element.classList.contains('btnNextPage')) {
|
||||||
|
element = view.querySelector('.btnNextPage');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element && focusManager.isCurrentlyFocusable(element)) {
|
||||||
|
focusManager.focus(element);
|
||||||
|
} else {
|
||||||
|
element = focusManager.autoFocus(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts listening for changes in the '.loading-spinner' HTML element
|
||||||
|
function installMutationObserver() {
|
||||||
|
var mutationObserver = new MutationObserver(function(mutations) {
|
||||||
|
mutations.forEach(function(mutation) {
|
||||||
|
console.debug(mutation.type);
|
||||||
|
if (mutation.target.classList.contains('hide')) {
|
||||||
|
onPageLoad();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var spinner = document.querySelector('.loading-spinner');
|
||||||
|
|
||||||
|
if (spinner) {
|
||||||
|
mutationObserver.observe(spinner, { attributes : true });
|
||||||
|
document.removeEventListener('viewshow', installMutationObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('viewshow', installMutationObserver);
|
||||||
|
|
||||||
|
window.addEventListener('pushState', function(e) {
|
||||||
|
|
||||||
|
// Reset history on some pages
|
||||||
|
|
||||||
|
var path = e.arguments && e.arguments[2] ? e.arguments[2] : '';
|
||||||
|
var pos = path.indexOf('?');
|
||||||
|
path = path.substring(0, pos !== -1 ? pos : path.length);
|
||||||
|
|
||||||
|
switch (path) {
|
||||||
|
case '#!/home.html':
|
||||||
|
if (!historyStartup || historyStartup !== path) {
|
||||||
|
historyStartup = path;
|
||||||
|
historyDepth = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '#!/selectserver.html':
|
||||||
|
case '#!/login.html':
|
||||||
|
historyStartup = path;
|
||||||
|
historyDepth = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
historyDepth++;
|
||||||
|
|
||||||
|
isRestored = false;
|
||||||
|
|
||||||
|
//console.log('history: ' + historyDepth + ', ' + historyStartup);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('popstate', function() {
|
||||||
|
historyDepth--;
|
||||||
|
isRestored = true;
|
||||||
|
//console.log('history: ' + historyDepth + ', ' + historyStartup);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add 'pushState' and 'replaceState' events
|
||||||
|
var _wr = function(type) {
|
||||||
|
var orig = history[type];
|
||||||
|
return function() {
|
||||||
|
var rv = orig.apply(this, arguments);
|
||||||
|
var e = new Event(type);
|
||||||
|
e.arguments = arguments;
|
||||||
|
window.dispatchEvent(e);
|
||||||
|
return rv;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
history.pushState = _wr('pushState');
|
||||||
|
history.replaceState = _wr('replaceState');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user