WebOS audio tracks support

This commit is contained in:
kolsys
2021-09-26 14:38:37 +03:00
parent d109cf5478
commit 3f31ff9536
6 changed files with 1150 additions and 401 deletions

View File

@@ -2,11 +2,11 @@ package msx
import _ "embed"
//go:embed pages/html5x.html
var Msxhtml5xhtml []byte
//go:embed pages/tizen.html
var Msxtizenhtml []byte
//go:embed pages/tizen.js
var Msxtizenjs []byte
//go:embed pages/tvx-plugin.min.js
var Msxtvxpluginminjs []byte

View File

@@ -66,6 +66,8 @@ type msxPage struct {
Items []gin.H `json:"items,omitempty"`
}
const ICON_OPTIONS = "tune"
func msxStart(c *gin.Context) {
c.JSON(200, gin.H{
"name": "TorrServer",
@@ -173,6 +175,7 @@ func msxPlaylist(c *gin.Context) {
status := tor.Status()
viewed := sets.ListViewed(hash)
var list []msxItem
contentAction := ""
for _, f := range status.FileStats {
mime := utils.GetMimeType(f.Path)
@@ -196,24 +199,16 @@ func msxPlaylist(c *gin.Context) {
"uri": uri,
"type": mime,
}
} else if platform == "lg" {
// TODO - custom player needed
//item.Action = "system:lg:launch:com.webos.app.mediadiscovery"
//item.Data = gin.H{
// "properties": gin.H{
// "videoList": gin.H{
// "result": [1]gin.H{
// gin.H{
// "url": uri,
// "thumbnail": tor.Poster,
// },
// },
// },
// },
//}
} else if platform == "tizen" {
contentAction = "content:request:interaction:init@" + host + "/msx/tizen.html"
} else if platform == "netcast" {
contentAction = "system:netcast:menu"
} else if platform == "ios" || platform == "mac" {
// TODO - for iOS and Mac the application must be defined in scheme but we don't know what user has installed
item.Action = "system:tvx:launch:vlc://" + uri
} else {
item.Action = "video:plugin:" + host + "/msx/html5x.html?url= " + url.QueryEscape(uri)
contentAction = "panel:request:player:options"
}
if isViewed(viewed, f.Id) {
@@ -247,23 +242,13 @@ func msxPlaylist(c *gin.Context) {
Items: list,
}
if platform == "tizen" {
if contentAction != "" {
res.Template.Properties = gin.H{
"button:content:icon": "tune",
"button:content:action": "content:request:interaction:init@" + host + "/msx/tizen.html",
}
} else if platform == "netcast" {
res.Template.Properties = gin.H{
"button:content:icon": "tune",
"button:content:action": "system:netcast:menu",
"button:content:icon": ICON_OPTIONS,
"button:content:action": contentAction,
}
}
// If only one item start to play immediately but it not works
// if (len(list) == 1) {
// res.Action = "execute:" + list[0].Action
// }
c.JSON(200, res)
}

View File

@@ -0,0 +1,763 @@
<!DOCTYPE html>
<html>
<head>
<title>HTML5 Extended Video Plugin</title>
<meta charset="UTF-8" />
<meta name="author" content="Benjamin Zachey" />
<meta name="contact" content="admin@benzac.de" />
<meta name="copyright" content="Benjamin Zachey" />
<style>
body
{
margin: 0px;
padding: 0px;
background: black;
font-family: sans-serif;
color: white;
}
div,label,img,input,span,i,b,p,video,audio,object,h1,h2,h3,pre,canvas,iframe
{
position: absolute;
margin: 0px;
padding: 0px;
border: 0px;
}
video,.video,.filler
{
width: 100%;
height: 100%;
}
.fullscreen
{
left: 0px;
top: 0px;
right: 0px;
bottom: 0px;
}
.content-screen
{
left: 64px;
right: 64px;
top: 36px;
bottom: 36px;
}
.plugin-object
{
visibility: hidden;
width: 0px;
height: 0px;
}
</style>
<script type="text/javascript" src="tvx-plugin.min.js"></script>
<script type="text/javascript">
/******************************************************************************/
//HTML5 Extended Video Plugin v0.0.5
//(c) 2021 Benjamin Zachey
//related API: https://www.w3.org/TR/2011/WD-html5-20110113/video.html
/******************************************************************************/
function Html5XPlayer() {
var player = null;
var ready = false;
var ended = false;
//--------------------------------------------------------------------------
//Audio & Subtitle Tracks
//--------------------------------------------------------------------------
var PROPERTY_PREFIX = "html5x:";
var SUBTITLE_KIND = "subtitles";
var PROXY_URL = TVXTools.getHostUrl("services/proxy.php?url={URL}");
var useProxy = false;
var showRelatedContent = false;
var hasRelatedContent = false;
var defaultAudioTrackLanguage = null;
var defaultSubtitleTrackIndex = -1;
var audioTrackIndicator = null;
var subtitleTrackIndicator = null;
var setupCrossOrigin = function(info) {
if (player != null) {
if (TVXPropertyTools.getBool(info, PROPERTY_PREFIX + "cors", true)) {
player.crossOrigin = "anonymous";
} else {
useProxy = true;
}
}
};
var setupRelatedContent = function(info) {
showRelatedContent = TVXPropertyTools.getBool(info, PROPERTY_PREFIX + "content", false);
hasRelatedContent = info != null && info.index >= 0;
};
var hasAudioTracks = function() {
return player != null && player.audioTracks != null && player.audioTracks.length > 0;
};
var hasTextTracks = function() {
return player != null && player.textTracks != null && player.textTracks.length > 0;
};
var foreachAudioTrack = function(callback) {
if (hasAudioTracks() && typeof callback == "function") {
var tracks = player.audioTracks;
var length = player.audioTracks.length;
for (var i = 0; i < length; i++) {
if (callback(i, tracks[i]) === true) {
break;
}
}
}
};
var foreachSubtitleTrack = function(callback) {
if (hasTextTracks() && typeof callback == "function") {
var tracks = player.textTracks;
var length = player.textTracks.length;
for (var i = 0; i < length; i++) {
var track = tracks[i];
if (track.kind === SUBTITLE_KIND && callback(i, tracks[i]) === true) {
break;
}
}
}
};
var isAudioTrackSelected = function(track) {
return track != null && track.enabled === true;
};
var isSubtitleTrackSelected = function(track) {
return track != null && track.mode === "showing";
};
var createIndexTrack = function(index, track) {
if (index >= 0 && track != null) {
return {
index: index,
track: track
};
}
return null;
};
var getAudioTrackLabel = function(indexTrack) {
var index = indexTrack != null ? indexTrack.index : -1;
var track = indexTrack != null ? indexTrack.track : null;
if (index >= 0 && track != null) {
return (TVXTools.isFullStr(track.label) ? track.label : "Audio Track " + (index + 1)) +
(TVXTools.isFullStr(track.language) ? " (" + track.language.toUpperCase() + ")" : "");
}
return hasAudioTracks() ? "None" : "Original";
};
var getSubtitleTrackLabel = function(indexTrack) {
var index = indexTrack != null ? indexTrack.index : -1;
var track = indexTrack != null ? indexTrack.track : null;
if (index >= 0 && track != null) {
return (TVXTools.isFullStr(track.label) ? track.label : "Subtitles " + (index + 1)) +
(TVXTools.isFullStr(track.language) ? " (" + track.language.toUpperCase() + ")" : "");
}
return "Off";
};
var storeAudioTrack = function(track) {
if (track != null && TVXTools.isFullStr(track.language)) {
TVXServices.storage.set(PROPERTY_PREFIX + "audiotrack", track.language);
} else {
TVXServices.storage.remove(PROPERTY_PREFIX + "audiotrack");
}
};
var storeSubtitleTrack = function(track) {
if (track != null && TVXTools.isFullStr(track.language)) {
TVXServices.storage.set(PROPERTY_PREFIX + "subtitle", track.language);
} else {
TVXServices.storage.remove(PROPERTY_PREFIX + "subtitle");
}
};
var setupAudioTrackIndicator = function(track) {
if (track != null && TVXTools.isFullStr(track.language)) {
audioTrackIndicator = "{ico:msx-white:audiotrack} " + track.language.toUpperCase();
} else {
audioTrackIndicator = null;
}
};
var setupSubtitleTrackIndicator = function(track) {
if (track != null && TVXTools.isFullStr(track.language)) {
subtitleTrackIndicator = "{ico:msx-white:subtitles} " + track.language.toUpperCase();
} else {
subtitleTrackIndicator = null;
}
};
var applyIndicators = function() {
if (audioTrackIndicator != null && subtitleTrackIndicator != null) {
TVXVideoPlugin.setupExtensionLabel(audioTrackIndicator + " " + subtitleTrackIndicator);
} else if (audioTrackIndicator) {
TVXVideoPlugin.setupExtensionLabel(audioTrackIndicator);
} else if (subtitleTrackIndicator) {
TVXVideoPlugin.setupExtensionLabel(subtitleTrackIndicator);
} else {
TVXVideoPlugin.setupExtensionLabel(null);
}
};
var selectAudioTrack = function(trackIndex, store, apply) {
var selectedTrack = null;
foreachAudioTrack(function(index, track) {
if (index == trackIndex) {
selectedTrack = track;
track.enabled = true;
} else {
track.enabled = false;
}
});
setupAudioTrackIndicator(selectedTrack);
if (store === true) {
storeAudioTrack(selectedTrack);
}
if (apply === true) {
applyIndicators();
}
};
var selectSubtitleTrack = function(trackIndex, store, apply) {
var selectedTrack = null;
foreachSubtitleTrack(function(index, track) {
if (index == trackIndex) {
selectedTrack = track;
track.mode = "showing";
} else {
track.mode = "disabled";
}
});
setupSubtitleTrackIndicator(selectedTrack);
if (store === true) {
storeSubtitleTrack(selectedTrack);
}
if (apply === true) {
applyIndicators();
}
};
var getDefaultAudioTrackIndex = function() {
var trackIndex = -1;
var fallbackTrackIndex = -1;
foreachAudioTrack(function(index, track) {
if (fallbackTrackIndex == -1) {
//Fallback to first audio track
fallbackTrackIndex = index;
}
if (defaultAudioTrackLanguage != null && defaultAudioTrackLanguage === track.language) {
trackIndex = index;
return true;//break
}
});
return trackIndex >= 0 ? trackIndex : fallbackTrackIndex;
};
var getSelectedAudioIndexTrack = function() {
var indexTrack = null;
foreachAudioTrack(function(index, track) {
if (isAudioTrackSelected(track)) {
indexTrack = createIndexTrack(index, track);
return true;//break
}
});
return indexTrack;
};
var getSelectedSubtitleIndexTrack = function() {
var indexTrack = null;
foreachSubtitleTrack(function(index, track) {
if (isSubtitleTrackSelected(track)) {
indexTrack = createIndexTrack(index, track);
return true;//break
}
});
return indexTrack;
};
var hasSelectedSubtitleTrack = function() {
return getSelectedSubtitleIndexTrack() != null;
};
var setupAudioTracks = function(info) {
defaultAudioTrackLanguage = TVXPropertyTools.getFullStr(info, PROPERTY_PREFIX + "audiotrack", TVXServices.storage.get(PROPERTY_PREFIX + "audiotrack"));
if (defaultAudioTrackLanguage == "default") {
defaultAudioTrackLanguage = null;//Select first audio track
}
};
var processSubtitleTrackCues = function(cues) {
if (cues != null && cues.length > 0) {
var length = cues.length;
//Note: On some platforms (e.g. chrome browsers and android devices), this will have no effect
for (var i = 0; i < length; i++) {
var cue = cues[i];
cue.snapToLines = true;//Use integer number of lines (default is true)
cue.line = -3;//Move the cue up to get some space at the bottom (default is -1)
}
}
};
var applySubtitleTrackCues = function() {
foreachSubtitleTrack(function(index, track) {
track.oncuechange = function() {
processSubtitleTrackCues(this.activeCues);
};
});
};
var secureSubtitleSource = function(src) {
return TVXTools.isSecureContext() ? TVXTools.secureUrl(src) : src;
};
var createSubtitleSource = function(src) {
return useProxy && TVXTools.isHttpUrl(src) ? TVXTools.strReplace(PROXY_URL, "{URL}", TVXTools.strToUrlStr(src)) : src;
};
var createSubtitleTrack = function(subtitle, src) {
if (TVXTools.isFullStr(subtitle) && TVXTools.isFullStr(src)) {
var separator = subtitle.indexOf(":");
if (separator > 0) {
return {
label: subtitle.substr(separator + 1),
language: subtitle.substr(0, separator),
src: secureSubtitleSource(createSubtitleSource(src))
};
}
}
return null;
};
var completeSubtitleTracks = function(completeState, tracks, callback) {
if (completeState != null) {
completeState.size--;
if (completeState.size == 0 && typeof callback == "function") {
callback(tracks);
}
}
};
var resolveSubtitleTrack = function(completeState, track, tracks, callback) {
if (track != null && !TVXTools.isHttpUrl(track.src)) {
TVXVideoPlugin.requestInteractionResponse(track.src, function(data) {
if (TVXTools.isFullStr(data.error)) {
TVXVideoPlugin.error(data.error);
} else if (data.response != null && TVXTools.isHttpUrl(data.response.url)) {
track.src = createSubtitleSource(data.response.url);
} else {
TVXVideoPlugin.warn("Track URL is missing or invalid");
}
completeSubtitleTracks(completeState, tracks, callback);
});
} else {
completeSubtitleTracks(completeState, tracks, callback);
}
};
var createSubtitleTracks = function(info, callback) {
var tracks = [];
var prefix = PROPERTY_PREFIX + "subtitle:";
var prefixLength = prefix.length;
var order = TVXPropertyTools.getFullStr(info, prefix + "order", null);
TVXPropertyTools.foreach(info, function(key, value) {
if (TVXTools.isFullStr(key) && key.indexOf(prefix) == 0) {
var track = createSubtitleTrack(key.substr(prefixLength), value);
if (track != null) {
tracks.push(track);
}
}
});
if (tracks.length > 1 && order != null) {
tracks.sort(function(track1, track2) {
if (order == "label") {
return track1.label.localeCompare(track2.label);
} else if (order == "language") {
return track1.language.localeCompare(track2.language);
}
return 0;
});
}
if (tracks.length > 0) {
var completeState = {
size: tracks.length
};
for (var i = 0; i < tracks.length; i++) {
resolveSubtitleTrack(completeState, tracks[i], tracks, callback);
}
} else if (typeof callback == "function") {
callback(tracks);
}
};
var setupSubtitleTracks = function(info, callback) {
if (player != null) {
createSubtitleTracks(info, function(tracks) {
defaultSubtitleTrackIndex = -1;
var html = "";
var defaultLanguage = TVXPropertyTools.getFullStr(info, PROPERTY_PREFIX + "subtitle", TVXServices.storage.get(PROPERTY_PREFIX + "subtitle"));
if (defaultLanguage == "default") {
defaultLanguage = null;//Switch off subtitles
}
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
var selected = false;
if (defaultLanguage != null && defaultLanguage == track.language) {
selected = true;
defaultSubtitleTrackIndex = i;
}
html += "<track" +
" kind='" + TVXTools.htmlAttrEscape(SUBTITLE_KIND) + "'" +
" label='" + TVXTools.htmlAttrEscape(track.label) + "'" +
" srclang='" + TVXTools.htmlAttrEscape(track.language) + "'" +
" src='" + TVXTools.htmlAttrEscape(track.src) + "'" +
(selected ? " default" : "") + "/>";
}
player.innerHTML = html;
applySubtitleTrackCues();
if (typeof callback == "function") {
callback();
}
});
} else {
if (typeof callback == "function") {
callback();
}
}
};
var setupVideoInfo = function(data, callback) {
var info = data != null && data.video != null ? data.video.info : null;
setupCrossOrigin(info);
setupRelatedContent(info);
setupAudioTracks(info);
setupSubtitleTracks(info, callback);
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Player Options
//--------------------------------------------------------------------------
var currentOptionsFocus = null;
var createTrackItem = function(type, index, label, selected) {
return {
focus: selected,
label: label,
extensionIcon: selected ? "check" : "blank",
action: selected ? "back" : "player:commit:message:" + type + ":" + index
};
};
var createAudioTracksPanel = function() {
var items = [];
if (hasAudioTracks()) {
foreachAudioTrack(function(index, track) {
items.push(createTrackItem("audiotrack", index, getAudioTrackLabel(createIndexTrack(index, track)), isAudioTrackSelected(track)));
});
} else {
items.push(createTrackItem("audiotrack", -1, getAudioTrackLabel(null), true));
}
return {
cache: false,
reuse: false,
headline: "Audio",
template: {
enumerate: false,
type: "control",
layout: "0,0,8,1"
},
items: items
};
};
var createSubtitleTracksPanel = function() {
var items = [createTrackItem("subtitle", -1, getSubtitleTrackLabel(null), !hasSelectedSubtitleTrack())];
foreachSubtitleTrack(function(index, track) {
items.push(createTrackItem("subtitle", index, getSubtitleTrackLabel(createIndexTrack(index, track)), isSubtitleTrackSelected(track)));
});
return {
cache: false,
reuse: false,
headline: "Subtitles",
template: {
enumerate: false,
type: "control",
layout: "0,0,8,1"
},
items: items
};
};
var createOptionsPanel = function() {
var selectedAudioIndexTrack = getSelectedAudioIndexTrack();
var selectedSubtitleIndexTrack = getSelectedSubtitleIndexTrack();
return {
cache: false,
reuse: false,
headline: "Options",
template: {
enumerate: false,
type: "control",
layout: "0,0,8,1"
},
items: [{
focus: currentOptionsFocus == "audiotrack",
id: "audiotrack",
icon: "audiotrack",
label: "Audio",
extensionLabel: getAudioTrackLabel(selectedAudioIndexTrack),
action: "[player:commit:message:focus:audiotrack|panel:request:player:audiotrack]"
}, {
focus: currentOptionsFocus == "subtitle",
id: "subtitle",
icon: "subtitles",
label: "Subtitles",
extensionLabel: getSubtitleTrackLabel(selectedSubtitleIndexTrack),
action: "[player:commit:message:focus:subtitle|panel:request:player:subtitle]"
}, {
display: showRelatedContent,
enable: hasRelatedContent,
focus: currentOptionsFocus == "content",
id: "content",
icon: "pageview",
label: "Show related content",
action: "[player:commit:message:focus:content|player:content]"
}]
};
};
var handleMessage = function(message) {
if (TVXTools.isFullStr(message)) {
if (message.indexOf("focus:") == 0) {
currentOptionsFocus = message.substr(6);
} else if (message.indexOf("audiotrack:") == 0) {
selectAudioTrack(TVXTools.strToNum(message.substr(11), -1), true, true);
TVXVideoPlugin.executeAction("back");
} else if (message.indexOf("subtitle:") == 0) {
selectSubtitleTrack(TVXTools.strToNum(message.substr(9), -1), true, true);
TVXVideoPlugin.executeAction("back");
} else {
TVXVideoPlugin.warn("Unknown plugin message: '" + message + "'");
}
}
};
var createResponseData = function(dataId) {
if (TVXTools.isFullStr(dataId)) {
if (dataId == "options") {
return createOptionsPanel();
} else if (dataId == "audiotrack") {
return createAudioTracksPanel();
} else if (dataId == "subtitle") {
return createSubtitleTracksPanel();
}
}
return null;
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Event Callbacks
//--------------------------------------------------------------------------
var onWaiting = function() {
TVXVideoPlugin.startLoading();
};
var onPlaying = function() {
TVXVideoPlugin.stopLoading();
TVXVideoPlugin.setState(TVXVideoState.PLAYING);
};
var onPaused = function() {
TVXVideoPlugin.stopLoading();
TVXVideoPlugin.setState(TVXVideoState.PAUSED);
};
var onContinue = function() {
TVXVideoPlugin.stopLoading();
};
var onReady = function() {
if (player != null && !ready) {
ready = true;
TVXVideoPlugin.debug("Video ready");
selectAudioTrack(getDefaultAudioTrackIndex(), false, false);
selectSubtitleTrack(defaultSubtitleTrackIndex, false, true);
TVXVideoPlugin.applyVolume();
TVXVideoPlugin.stopLoading();
TVXVideoPlugin.startPlayback(true);//Accelerated start
}
};
var getErrorText = function(code) {
if (code == 1) {
//The fetching of the associated resource was aborted by the user's request.
return "Playback Aborted";
} else if (code == 2) {
//Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.
return "Network Error";
} else if (code == 3) {
//Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.
return "Media Decode Error";
} else if (code == 4) {
//The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable.
return "Source Not Supported";
}
return "Unknown Error";
};
var getErrorMessage = function(code, message) {
var msg = code + ": " + getErrorText(code);
if (TVXTools.isFullStr(message)) {
msg += ": " + message;
}
return msg;
};
var onError = function() {
if (player != null && player.error != null) {
TVXVideoPlugin.error("Video error: " + getErrorMessage(player.error.code, player.error.message));
TVXVideoPlugin.stopLoading();
}
};
var onEnded = function() {
if (!ended) {
ended = true;
TVXVideoPlugin.debug("Video ended");
TVXVideoPlugin.stopPlayback();
}
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Helper Functions
//--------------------------------------------------------------------------
var setupVideoWithId = function(id) {
if (TVXTools.isFullStr(id)) {
TVXVideoPlugin.requestInteractionResponse(id, function(data) {
if (TVXTools.isFullStr(data.error)) {
TVXVideoPlugin.error(data.error);
TVXVideoPlugin.stopLoading();
} else if (!setupVideoWithUrl(data.response != null ? data.response.url : null)) {
TVXVideoPlugin.warn("Video URL is missing");
TVXVideoPlugin.stopLoading();
}
});
return true;
}
return false;
};
var setupVideoWithUrl = function(url) {
//Note: URL does not need to be an HTTP/HTTPS URL (it can be any URL)
if (TVXTools.isFullStr(url)) {
TVXVideoPlugin.requestData("video:info", function(data) {
setupVideoInfo(data, function() {
player.src = url;
player.load();
});
});
return true;
}
return false;
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//Player Interface
//--------------------------------------------------------------------------
this.init = function() {
player = document.getElementById("player");
player.addEventListener("canplay", onReady);
player.addEventListener("error", onError);
player.addEventListener("ended", onEnded);
player.addEventListener("waiting", onWaiting);
player.addEventListener("play", onContinue);
player.addEventListener("playing", onPlaying);
player.addEventListener("pause", onPaused);
player.addEventListener("seeked", onContinue);
player.addEventListener("abort", onContinue);
};
this.ready = function() {
if (player != null) {
TVXVideoPlugin.debug("Video plugin ready");
TVXVideoPlugin.startLoading();
if (!setupVideoWithId(TVXServices.urlParams.get("id")) &&
!setupVideoWithUrl(TVXServices.urlParams.get("url"))) {
TVXVideoPlugin.warn("Video ID or URL is missing");
TVXVideoPlugin.stopLoading();
}
} else {
TVXVideoPlugin.error("Video player is not initialized");
}
};
this.dispose = function() {
if (player != null) {
player.removeEventListener("canplay", onReady);
player.removeEventListener("error", onError);
player.removeEventListener("ended", onEnded);
player.removeEventListener("waiting", onWaiting);
player.removeEventListener("play", onContinue);
player.removeEventListener("playing", onPlaying);
player.removeEventListener("pause", onPaused);
player.removeEventListener("seeked", onContinue);
player.removeEventListener("abort", onContinue);
player = null;
}
};
this.play = function() {
if (player != null) {
player.play();
}
};
this.pause = function() {
if (player != null) {
player.pause();
}
};
this.stop = function() {
if (player != null) {
//Note: Html5 player does not support stop -> use pause
player.pause();
}
};
this.getDuration = function() {
if (player != null) {
return player.duration;
}
return 0;
};
this.getPosition = function() {
if (player != null) {
return player.currentTime;
}
return 0;
};
this.setPosition = function(position) {
if (player != null) {
player.currentTime = position;
}
};
this.setVolume = function(volume) {
if (player != null) {
player.volume = volume / 100;
}
};
this.getVolume = function() {
if (player != null) {
return player.volume * 100;
}
return 100;
};
this.setMuted = function(muted) {
if (player != null) {
player.muted = muted;
}
};
this.isMuted = function() {
if (player != null) {
return player.muted;
}
return false;
};
this.getSpeed = function() {
if (player != null) {
return player.playbackRate;
}
return 1;
};
this.setSpeed = function(speed) {
if (player != null) {
player.playbackRate = speed;
}
};
this.getUpdateData = function() {
return {
position: this.getPosition(),
duration: this.getDuration(),
speed: this.getSpeed()
};
};
this.handleData = function(data) {
handleMessage(data.message);
};
this.handleRequest = function(dataId, data, callback) {
callback(createResponseData(dataId));
};
//--------------------------------------------------------------------------
}
/******************************************************************************/
/******************************************************************************/
//Setup
/******************************************************************************/
window.onload = function() {
TVXVideoPlugin.setupPlayer(new Html5XPlayer());
TVXVideoPlugin.init();
};
/******************************************************************************/
</script>
</head>
<body>
<video id="player" playsinline></video>
</body>
</html>

View File

@@ -7,7 +7,373 @@
<meta name="contact" content="admin@benzac.de" />
<meta name="copyright" content="Benjamin Zachey" />
<script type="text/javascript" src="tvx-plugin.min.js"></script>
<script type="text/javascript" src="tizen.js"></script>
<script type="text/javascript">
/******************************************************************************/
//Tizen Interaction Plugin v0.0.1
//(c) 2021 Benjamin Zachey
//Action syntax example:
//- "content:request:interaction:init@http://msx.benzac.de/interaction/tizen.html"
/******************************************************************************/
/******************************************************************************/
//TizenPlayer
/******************************************************************************/
function TizenPlayer() {
var pushToArray = function(array, items) {
if (array != null && items != null) {
if (Array.isArray(items)) {
for (var i = 0; i < items.length; i++) {
array.push(items[i]);
}
} else {
array.push(items);
}
}
};
var getValueLabel = function(value) {
if (typeof value == "string") {
return TVXTools.strFullCheck(value, "-");
}
return value != null ? TVXTools.strValue(value) : "-";
};
var getPropertyLabel = function(value, unit) {
if (TVXTools.isFullStr(value) && value.indexOf("|") >= 0) {
return value.split("|")[0];
}
if (value != null) {
if (unit != null) {
return value + " " + unit;
}
return TVXTools.strValue(value);
}
return "Unknown";
};
var getPropertyValue = function(value) {
if (TVXTools.isFullStr(value) && value.indexOf("|") >= 0) {
value = value.split("|")[1];
}
if (TVXTools.isFullStr(value) && value.indexOf("num:") == 0) {
return TVXTools.strToNum(value.substr(4));
}
return value;
};
var getTrackLabel = function(track) {
if (track != null) {
var prefix = "Track " + track.index;
var suffix = track.info != null ? track.info.language : null;//Audio track
if (suffix == null) {
suffix = track.info != null ? track.info.track_lang : null;//Text track
}
return TVXTools.isFullStr(suffix) ? prefix + " (" + suffix + ")" : prefix;
}
return "None";
};
var createPropertyControls = function(y, propertyIcon, propertyLabel, propertyKey, propertyValue, propertyUnit, availableValues, nextButton) {
var panelItems = [];
var selectedPropertyLabel = getPropertyLabel(propertyValue, propertyUnit);
var firstPropertyValue = null;
var nextPropertyValue = null;
var selectNext = false;
if (availableValues != null) {
for (var i = 0; i < availableValues.length; i++) {
var value = getPropertyValue(availableValues[i]);
var label = getPropertyLabel(availableValues[i], propertyUnit);
var selected = value === propertyValue;
if (firstPropertyValue == null) {
firstPropertyValue = value;
}
if (selected) {
selectNext = true;
selectedPropertyLabel = label;
} else if (selectNext) {
selectNext = false;
nextPropertyValue = value;
}
panelItems.push({
focus: selected,
extensionIcon: selected ? "check" : "blank",
label: label,
action: selected ? "back" : "[invalidate:content|back|player:commit]",
data: {
key: propertyKey,
value: value,
action: "reload:content"
}
});
}
}
if (nextPropertyValue == null) {
nextPropertyValue = firstPropertyValue;
}
return [{
enable: panelItems.length > 0,
type: "control",
layout: "0," + y + "," + (nextButton ? "7,1" : "8,1"),
icon: propertyIcon,
label: propertyLabel,
extensionLabel: selectedPropertyLabel,
action: "panel:data",
data: {
headline: propertyLabel,
compress: panelItems.length > 6,
template: {
type: "control",
enumerate: false,
layout: panelItems.length > 8 ? "0,0,5,1" : panelItems.length > 6 ? "0,0,10,1" : "0,0,8,1"
},
items: panelItems
}
}, {
display: nextButton,
enable: nextPropertyValue != null,
type: "button",
icon: "navigate-next",
iconSize: "small",
layout: "7," + y + ",1,1",
action: "[invalidate:content|player:commit]",
data: {
key: propertyKey,
value: nextPropertyValue,
action: "reload:content"
}
}];
};
var createTrackControl = function(y, propertyIcon, propertyLabel, propertyKey, currentTrack, availableTracks) {
var panelItems = [];
if (availableTracks != null) {
for (var i = 0; i < availableTracks.length; i++) {
var track = availableTracks[i];
var selected = track.index === (currentTrack != null ? currentTrack.index : -1);
panelItems.push({
focus: selected,
extensionIcon: selected ? "check" : "blank",
label: getTrackLabel(track),
action: selected ? "back" : "[invalidate:content|back|player:commit]",
data: {
key: propertyKey,
value: track.index,
action: "reload:content"
}
});
}
}
return {
enable: panelItems.length > 0,
type: "control",
layout: "0," + y + ",8,1",
icon: propertyIcon,
label: propertyLabel,
extensionLabel: getTrackLabel(currentTrack),
action: "panel:data",
data: {
headline: propertyLabel,
compress: panelItems.length > 6,
template: {
type: "control",
enumerate: false,
layout: panelItems.length > 8 ? "0,0,5,1" : panelItems.length > 6 ? "0,0,10,1" : "0,0,8,1"
},
items: panelItems
}
};
};
var createControlItems = function(infoData) {
var items = [];
pushToArray(items, createPropertyControls(0, "featured-video", "Display Area", "tizen:display:area",
infoData && infoData.display != null ? infoData.display.area : null, null, [
"21x9|0,0.119,1,0.762",
"16x9 (Default)|0,0,1,1",
"4x3|0.125,0,0.75,1"
], true));
pushToArray(items, createPropertyControls(1, "aspect-ratio", "Display Mode", "tizen:display:mode",
infoData != null && infoData.display != null ? infoData.display.mode : null, null, [
"Fit Screen (Default)|PLAYER_DISPLAY_MODE_LETTER_BOX",
"Fill Screen|PLAYER_DISPLAY_MODE_FULL_SCREEN",
"Auto Aspect Ratio|PLAYER_DISPLAY_MODE_AUTO_ASPECT_RATIO"
], true));
pushToArray(items, createPropertyControls(2, "av-timer", "Buffer Timeout", "tizen:buffer:timeout",
infoData != null && infoData.buffer != null ? infoData.buffer.timeout : null, "sec", [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, "20 sec (Default)|num:20", 25, 30, 40
], false));
pushToArray(items, createPropertyControls(3, "timelapse", "Buffer Size (Init)", "tizen:buffer:size:init",
infoData != null && infoData.buffer != null && infoData.buffer.size != null ? infoData.buffer.size.init : null, "sec", null, false));
pushToArray(items, createPropertyControls(4, "timelapse", "Buffer Size (Resume)", "tizen:buffer:size:resume",
infoData != null && infoData.buffer != null && infoData.buffer.size != null ? infoData.buffer.size.resume : null, "sec", null, false));
pushToArray(items, createTrackControl(5, "audiotrack", "Audio Track", "tizen:track:audio",
infoData != null && infoData.stream != null ? infoData.stream.audio : null,
infoData != null && infoData.tracks != null ? infoData.tracks.audio : null));
pushToArray(items, createTrackControl(6, "subtitles", "Text Track", "tizen:track:text",
infoData != null && infoData.stream != null ? infoData.stream.text : null,
infoData != null && infoData.tracks != null ? infoData.tracks.text : null));
return items;
};
var createInfoItems = function(infoData) {
var infoKeys = "-";
var infoValues = "-";
if (infoData != null) {
infoKeys = "Version:{br}State:{br}{br}";
infoValues = getValueLabel(infoData.version) + "{br}" +
getValueLabel(infoData.state) + "{br}{br}";
if (infoData.stream != null) {
if (infoData.stream.video != null && infoData.stream.video.info != null) {
for (var key in infoData.stream.video.info) {
infoKeys += "Video [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.video.info[key]) + "{br}";
}
infoKeys += "{br}";
infoValues += "{br}";
}
if (infoData.stream.audio != null && infoData.stream.audio.info != null) {
for (var key in infoData.stream.audio.info) {
infoKeys += "Audio [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.audio.info[key]) + "{br}";
}
infoKeys += "{br}";
infoValues += "{br}";
}
if (infoData.stream.text != null && infoData.stream.text.info != null) {
for (var key in infoData.stream.text.info) {
infoKeys += "Text [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.text.info[key]) + "{br}";
}
}
}
}
return [{
type: "space",
layout: "8,0,3,8",
offset: "0.25,0,0,0",
truncation: "text",
text: infoKeys
}, {
type: "space",
layout: "11,0,5,8",
offset: "0.25,0,-0.25,0",
truncation: "text",
text: "{col:msx-white}" + infoValues,
live: {
type: "airtime",
duration: 2000,
over: {
action: "reload:content"
}
}
}];
};
var createContentItems = function(infoData) {
var items = [];
pushToArray(items, createControlItems(infoData));
pushToArray(items, createInfoItems(infoData));
return items;
};
var createContent = function(infoData) {
return {
cache: false,
compress: true,
type: "pages",
headline: "Tizen Player",
extension: "{ico:msx-white:timer} " + TVXDateTools.getTimestamp(),
pages: [{
items: createContentItems(infoData)
}]
};
};
var createWarningContent = function(playerInfo) {
return {
type: "pages",
headline: "Tizen Player",
pages: [{
items: [{
type: "default",
layout: "0,0,12,6",
color: "msx-glass",
headline: "{ico:msx-yellow:warning} Player Not Available",
text: "Tizen player is required for this plugin. Current player is: {txt:msx-white:" + playerInfo + "}."
}]
}]
};
};
var createDummyData = function() {
return {
version: "1.0",
state: "PLAYING",
display: {
area: "0,0,1,1",
mode: "PLAYER_DISPLAY_MODE_LETTER_BOX"
},
buffer: {
timeout: 20,
size: {
init: 10,
resume: 10
}
},
stream: {
video: {
index: 0,
info: {
fourCC: "h264",
Width: 1280,
Height: 720,
Bit_rate: 128000
}
}
}
};
};
this.handleRequest = function(playerInfo, dataId, callback) {
if (dataId == "init") {
if (TVXTools.isFullStr(playerInfo) && (playerInfo == "tizen" || playerInfo.indexOf("tizen/") == 0)) {
TVXInteractionPlugin.requestPlayerResponse("tizen:info", function(data) {
callback(createContent(data.response != null && data.response.tizen != null ? data.response.tizen.info : null));
});
} else {
callback(createWarningContent(playerInfo));
}
return true;
} else if (dataId == "dummy") {
callback(createContent(createDummyData()));
return true;
}
return false;
};
}
/******************************************************************************/
/******************************************************************************/
//TizenHandler
/******************************************************************************/
function TizenHandler() {
var playerInfo = null;
var readyService = new TVXBusyService();
var player = new TizenPlayer();
this.ready = function() {
readyService.start();
TVXInteractionPlugin.requestData("info:base", function(data) {
playerInfo = TVXTools.strFullCheck(data.info != null ? data.info.player : null, "unknown");
readyService.stop();
});
};
this.handleRequest = function(dataId, data, callback) {
readyService.onReady(function() {
if (!player.handleRequest(playerInfo, dataId, callback)) {
callback();
}
});
};
}
/******************************************************************************/
/******************************************************************************/
//Setup
/******************************************************************************/
window.onload = function() {
TVXInteractionPlugin.setupHandler(new TizenHandler());
TVXInteractionPlugin.init();
};
/******************************************************************************/
</script>
</head>
<body>
</body>

View File

@@ -1,365 +0,0 @@
/******************************************************************************/
//Tizen Interaction Plugin v0.0.1
//(c) 2021 Benjamin Zachey
//Action syntax example:
//- "content:request:interaction:init@http://msx.benzac.de/interaction/tizen.html"
/******************************************************************************/
/******************************************************************************/
//TizenPlayer
/******************************************************************************/
function TizenPlayer() {
var pushToArray = function(array, items) {
if (array != null && items != null) {
if (Array.isArray(items)) {
for (var i = 0; i < items.length; i++) {
array.push(items[i]);
}
} else {
array.push(items);
}
}
};
var getValueLabel = function(value) {
if (typeof value == "string") {
return TVXTools.strFullCheck(value, "-");
}
return value != null ? TVXTools.strValue(value) : "-";
};
var getPropertyLabel = function(value, unit) {
if (TVXTools.isFullStr(value) && value.indexOf("|") >= 0) {
return value.split("|")[0];
}
if (value != null) {
if (unit != null) {
return value + " " + unit;
}
return TVXTools.strValue(value);
}
return "Unknown";
};
var getPropertyValue = function(value) {
if (TVXTools.isFullStr(value) && value.indexOf("|") >= 0) {
value = value.split("|")[1];
}
if (TVXTools.isFullStr(value) && value.indexOf("num:") == 0) {
return TVXTools.strToNum(value.substr(4));
}
return value;
};
var getTrackLabel = function(track) {
if (track != null) {
var prefix = "Track " + track.index;
var suffix = track.info != null ? track.info.language : null;//Audio track
if (suffix == null) {
suffix = track.info != null ? track.info.track_lang : null;//Text track
}
return TVXTools.isFullStr(suffix) ? prefix + " (" + suffix + ")" : prefix;
}
return "None";
};
var createPropertyControls = function(y, propertyIcon, propertyLabel, propertyKey, propertyValue, propertyUnit, availableValues, nextButton) {
var panelItems = [];
var selectedPropertyLabel = getPropertyLabel(propertyValue, propertyUnit);
var firstPropertyValue = null;
var nextPropertyValue = null;
var selectNext = false;
if (availableValues != null) {
for (var i = 0; i < availableValues.length; i++) {
var value = getPropertyValue(availableValues[i]);
var label = getPropertyLabel(availableValues[i], propertyUnit);
var selected = value === propertyValue;
if (firstPropertyValue == null) {
firstPropertyValue = value;
}
if (selected) {
selectNext = true;
selectedPropertyLabel = label;
} else if (selectNext) {
selectNext = false;
nextPropertyValue = value;
}
panelItems.push({
focus: selected,
extensionIcon: selected ? "check" : "blank",
label: label,
action: selected ? "back" : "[invalidate:content|back|player:commit]",
data: {
key: propertyKey,
value: value,
action: "reload:content"
}
});
}
}
if (nextPropertyValue == null) {
nextPropertyValue = firstPropertyValue;
}
return [{
enable: panelItems.length > 0,
type: "control",
layout: "0," + y + "," + (nextButton ? "7,1" : "8,1"),
icon: propertyIcon,
label: propertyLabel,
extensionLabel: selectedPropertyLabel,
action: "panel:data",
data: {
headline: propertyLabel,
compress: panelItems.length > 6,
template: {
type: "control",
enumerate: false,
layout: panelItems.length > 8 ? "0,0,5,1" : panelItems.length > 6 ? "0,0,10,1" : "0,0,8,1"
},
items: panelItems
}
}, {
display: nextButton,
enable: nextPropertyValue != null,
type: "button",
icon: "navigate-next",
iconSize: "small",
layout: "7," + y + ",1,1",
action: "[invalidate:content|player:commit]",
data: {
key: propertyKey,
value: nextPropertyValue,
action: "reload:content"
}
}];
};
var createTrackControl = function(y, propertyIcon, propertyLabel, propertyKey, currentTrack, availableTracks) {
var panelItems = [];
if (availableTracks != null) {
for (var i = 0; i < availableTracks.length; i++) {
var track = availableTracks[i];
var selected = track.index === (currentTrack != null ? currentTrack.index : -1);
panelItems.push({
focus: selected,
extensionIcon: selected ? "check" : "blank",
label: getTrackLabel(track),
action: selected ? "back" : "[invalidate:content|back|player:commit]",
data: {
key: propertyKey,
value: track.index,
action: "reload:content"
}
});
}
}
return {
enable: panelItems.length > 0,
type: "control",
layout: "0," + y + ",8,1",
icon: propertyIcon,
label: propertyLabel,
extensionLabel: getTrackLabel(currentTrack),
action: "panel:data",
data: {
headline: propertyLabel,
compress: panelItems.length > 6,
template: {
type: "control",
enumerate: false,
layout: panelItems.length > 8 ? "0,0,5,1" : panelItems.length > 6 ? "0,0,10,1" : "0,0,8,1"
},
items: panelItems
}
};
};
var createControlItems = function(infoData) {
var items = [];
pushToArray(items, createPropertyControls(0, "featured-video", "Display Area", "tizen:display:area",
infoData && infoData.display != null ? infoData.display.area : null, null, [
"21x9|0,0.119,1,0.762",
"16x9 (Default)|0,0,1,1",
"4x3|0.125,0,0.75,1"
], true));
pushToArray(items, createPropertyControls(1, "aspect-ratio", "Display Mode", "tizen:display:mode",
infoData != null && infoData.display != null ? infoData.display.mode : null, null, [
"Fit Screen (Default)|PLAYER_DISPLAY_MODE_LETTER_BOX",
"Fill Screen|PLAYER_DISPLAY_MODE_FULL_SCREEN",
"Auto Aspect Ratio|PLAYER_DISPLAY_MODE_AUTO_ASPECT_RATIO"
], true));
pushToArray(items, createPropertyControls(2, "av-timer", "Buffer Timeout", "tizen:buffer:timeout",
infoData != null && infoData.buffer != null ? infoData.buffer.timeout : null, "sec", [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, "20 sec (Default)|num:20", 25, 30, 40
], false));
pushToArray(items, createPropertyControls(3, "timelapse", "Buffer Size (Init)", "tizen:buffer:size:init",
infoData != null && infoData.buffer != null && infoData.buffer.size != null ? infoData.buffer.size.init : null, "sec", null, false));
pushToArray(items, createPropertyControls(4, "timelapse", "Buffer Size (Resume)", "tizen:buffer:size:resume",
infoData != null && infoData.buffer != null && infoData.buffer.size != null ? infoData.buffer.size.resume : null, "sec", null, false));
pushToArray(items, createTrackControl(5, "audiotrack", "Audio Track", "tizen:track:audio",
infoData != null && infoData.stream != null ? infoData.stream.audio : null,
infoData != null && infoData.tracks != null ? infoData.tracks.audio : null));
pushToArray(items, createTrackControl(6, "subtitles", "Text Track", "tizen:track:text",
infoData != null && infoData.stream != null ? infoData.stream.text : null,
infoData != null && infoData.tracks != null ? infoData.tracks.text : null));
return items;
};
var createInfoItems = function(infoData) {
var infoKeys = "-";
var infoValues = "-";
if (infoData != null) {
infoKeys = "Version:{br}State:{br}{br}";
infoValues = getValueLabel(infoData.version) + "{br}" +
getValueLabel(infoData.state) + "{br}{br}";
if (infoData.stream != null) {
if (infoData.stream.video != null && infoData.stream.video.info != null) {
for (var key in infoData.stream.video.info) {
infoKeys += "Video [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.video.info[key]) + "{br}";
}
infoKeys += "{br}";
infoValues += "{br}";
}
if (infoData.stream.audio != null && infoData.stream.audio.info != null) {
for (var key in infoData.stream.audio.info) {
infoKeys += "Audio [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.audio.info[key]) + "{br}";
}
infoKeys += "{br}";
infoValues += "{br}";
}
if (infoData.stream.text != null && infoData.stream.text.info != null) {
for (var key in infoData.stream.text.info) {
infoKeys += "Text [" + key + "]:{br}";
infoValues += getValueLabel(infoData.stream.text.info[key]) + "{br}";
}
}
}
}
return [{
type: "space",
layout: "8,0,3,8",
offset: "0.25,0,0,0",
truncation: "text",
text: infoKeys
}, {
type: "space",
layout: "11,0,5,8",
offset: "0.25,0,-0.25,0",
truncation: "text",
text: "{col:msx-white}" + infoValues,
live: {
type: "airtime",
duration: 2000,
over: {
action: "reload:content"
}
}
}];
};
var createContentItems = function(infoData) {
var items = [];
pushToArray(items, createControlItems(infoData));
pushToArray(items, createInfoItems(infoData));
return items;
};
var createContent = function(infoData) {
return {
cache: false,
compress: true,
type: "pages",
headline: "Tizen Player",
extension: "{ico:msx-white:timer} " + TVXDateTools.getTimestamp(),
pages: [{
items: createContentItems(infoData)
}]
};
};
var createWarningContent = function(playerInfo) {
return {
type: "pages",
headline: "Tizen Player",
pages: [{
items: [{
type: "default",
layout: "0,0,12,6",
color: "msx-glass",
headline: "{ico:msx-yellow:warning} Player Not Available",
text: "Tizen player is required for this plugin. Current player is: {txt:msx-white:" + playerInfo + "}."
}]
}]
};
};
var createDummyData = function() {
return {
version: "1.0",
state: "PLAYING",
display: {
area: "0,0,1,1",
mode: "PLAYER_DISPLAY_MODE_LETTER_BOX"
},
buffer: {
timeout: 20,
size: {
init: 10,
resume: 10
}
},
stream: {
video: {
index: 0,
info: {
fourCC: "h264",
Width: 1280,
Height: 720,
Bit_rate: 128000
}
}
}
};
};
this.handleRequest = function(playerInfo, dataId, callback) {
if (dataId == "init") {
if (TVXTools.isFullStr(playerInfo) && (playerInfo == "tizen" || playerInfo.indexOf("tizen/") == 0)) {
TVXInteractionPlugin.requestPlayerResponse("tizen:info", function(data) {
callback(createContent(data.response != null && data.response.tizen != null ? data.response.tizen.info : null));
});
} else {
callback(createWarningContent(playerInfo));
}
return true;
} else if (dataId == "dummy") {
callback(createContent(createDummyData()));
return true;
}
return false;
};
}
/******************************************************************************/
/******************************************************************************/
//TizenHandler
/******************************************************************************/
function TizenHandler() {
var playerInfo = null;
var readyService = new TVXBusyService();
var player = new TizenPlayer();
this.ready = function() {
readyService.start();
TVXInteractionPlugin.requestData("info:base", function(data) {
playerInfo = TVXTools.strFullCheck(data.info != null ? data.info.player : null, "unknown");
readyService.stop();
});
};
this.handleRequest = function(dataId, data, callback) {
readyService.onReady(function() {
if (!player.handleRequest(playerInfo, dataId, callback)) {
callback();
}
});
};
}
/******************************************************************************/
/******************************************************************************/
//Setup
/******************************************************************************/
window.onload = function() {
TVXInteractionPlugin.setupHandler(new TizenHandler());
TVXInteractionPlugin.init();
};
/******************************************************************************/

View File

@@ -8,12 +8,12 @@ func SetupRoute(route *gin.RouterGroup) {
route.GET("/msx/playlist", msxPlaylist)
route.GET("/msx/playlist/*fname", msxPlaylist)
route.GET("/msx/tizen.html", func(c *gin.Context) {
c.Data(200, "text/html; charset=utf-8", Msxtizenhtml)
route.GET("/msx/html5x.html", func(c *gin.Context) {
c.Data(200, "text/html; charset=utf-8", Msxhtml5xhtml)
})
route.GET("/msx/tizen.js", func(c *gin.Context) {
c.Data(200, "text/javascript; charset=utf-8", Msxtizenjs)
route.GET("/msx/tizen.html", func(c *gin.Context) {
c.Data(200, "text/html; charset=utf-8", Msxtizenhtml)
})
route.GET("/msx/tvx-plugin.min.js", func(c *gin.Context) {