Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
121
gaia/tv_apps/dlna-player/index.html
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<script defer type="text/javascript" src="js/lib/ssdp/ssdp.js"></script>
|
||||
<script defer type="text/javascript" src="js/lib/plug.play/plug.upnp.js"></script>
|
||||
<script defer type="text/javascript" src="js/lib/plug.play/ContentDirectory/plug.upnp_contentdirectory.js"></script>
|
||||
<script defer type="text/javascript" src="js/lib/plug.play/ConnectionManager/plug.upnp_connectionmanager.js"></script>
|
||||
<script defer type="text/javascript" src="js/lib/plug.play/AVTransport/plug.upnp_avtransport.js"></script>
|
||||
<script defer type="text/javascript" src="js/lib/plug.play/RenderingControl/plug.upnp_renderingcontrol.js"></script>
|
||||
<script defer type="text/javascript" src="js/service_manager.js"></script>
|
||||
<script defer type="text/javascript" src="js/content_directory_manager.js"></script>
|
||||
<script defer type="text/javascript" src="js/connection_service_manager.js"></script>
|
||||
<script defer type="text/javascript" src="js/avtransport_manager.js"></script>
|
||||
<script defer type="text/javascript" src="js/device_manager.js"></script>
|
||||
<script defer type="text/javascript" src="js/dlna_player.js"></script>
|
||||
|
||||
<link href="shared/style/lists.css" rel="stylesheet">
|
||||
<link href="shared/style/headers.css" rel="stylesheet">
|
||||
<link href="shared/style/tabs.css" rel="stylesheet">
|
||||
<link href="shared/style/buttons.css" rel="stylesheet">
|
||||
|
||||
<link href="style/service_manager.css" rel="stylesheet">
|
||||
<link href="style/app.css" rel="stylesheet">
|
||||
</head>
|
||||
<body role="application" class="skin-dark">
|
||||
<section role="region" id="main" class="panel">
|
||||
<header>
|
||||
<ul role="tablist" class="bb-tablist">
|
||||
<li>
|
||||
<button id="remoteQuiet">
|
||||
<span class="icon quiet-icon">Volume -</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remoteLoud">
|
||||
<span class="icon loud-icon">Volume +</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>My DLNA Player</h1>
|
||||
</header>
|
||||
<article id="contentList">
|
||||
<header>
|
||||
<h2>Renderer List</h2>
|
||||
</header>
|
||||
<ul>
|
||||
<li class="server">
|
||||
<select name="select" id="AVTList" class="button icon-dialog">
|
||||
<option value="local">Local player</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
<header>
|
||||
<h2>Folder List</h2>
|
||||
</header>
|
||||
<ul id="folderList"></ul>
|
||||
<header style='display: none'>
|
||||
<h2>Renderer List</h2>
|
||||
</header>
|
||||
<ul id="rendererList" style='display: none'></ul>
|
||||
<header>
|
||||
<h2><del>Renderer Control List</del></h2>
|
||||
</header>
|
||||
<ul id="renderCtrlList" style='display: none'></ul>
|
||||
<div id="debug"></div>
|
||||
</article>
|
||||
|
||||
<ul id="playerTogglers" role="tablist" class="bb-tablist">
|
||||
<li>
|
||||
<button id="discoverButton"></button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="toPlayerPage"></button>
|
||||
</li>
|
||||
<li>
|
||||
<div id="trackInfoBar"> </div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="playerPanel" role="region" class="panel">
|
||||
<header>
|
||||
<h1>Player</h1>
|
||||
</header>
|
||||
<article>
|
||||
<audio id="audioPlayer" autoplay="true" class="player" controls="true"></audio>
|
||||
<video id="videoPlayer" autoplay="true" class="player" controls="true"></video>
|
||||
<iframe id="unknownPlayer" class="player"></iframe>
|
||||
<img id="imagePlayer" class="player"></img>
|
||||
</article>
|
||||
<ul id="playerTogglers" role="tablist" class="bb-tablist">
|
||||
<li><button id="toDirectoryPage"></button></li>
|
||||
</div>
|
||||
</section>
|
||||
<ul id="controlButtons" role="tablist" class="bb-tablist">
|
||||
<li>
|
||||
<button id="remotePrev">|◀</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remoteRewind">◀◀</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remotePlay">►</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remotePause">▐▐</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remoteStop">◼</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remoteFF">►►</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="remoteNext">►|</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
166
gaia/tv_apps/dlna-player/js/avtransport_manager.js
Executable file
|
|
@ -0,0 +1,166 @@
|
|||
/* globals ServiceManager, Plug */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
function AVTManager() {
|
||||
|
||||
}
|
||||
AVTManager.prototype = {
|
||||
__proto__: ServiceManager.prototype,
|
||||
|
||||
prevButton: document.getElementById('remotePrev'),
|
||||
rewindButton: document.getElementById('remoteRewind'),
|
||||
playButton: document.getElementById('remotePlay'),
|
||||
pauseButton: document.getElementById('remotePause'),
|
||||
stopButton: document.getElementById('remoteStop'),
|
||||
ffButton: document.getElementById('remoteFF'),
|
||||
nextButton: document.getElementById('remoteNext'),
|
||||
avtSelector: document.getElementById('AVTList'),
|
||||
trackInfoBar: document.getElementById('trackInfoBar'),
|
||||
|
||||
listElementId: 'AVTList',
|
||||
serviceType: 'upnp:urn:schemas-upnp-org:service:AVTransport:1',
|
||||
serviceConstructor: Plug.UPnP_AVTransport,
|
||||
|
||||
serverView: function avtm_serverView(serviceWrapper) {
|
||||
var option = document.createElement('option');
|
||||
option.value = serviceWrapper.svc.id;
|
||||
option.textContent = serviceWrapper.friendlyName;
|
||||
|
||||
return option;
|
||||
},
|
||||
get currentAVT() {
|
||||
var id = this.avtSelector[this.avtSelector.selectedIndex].value;
|
||||
return id == 'local' ? null : this.savedServices[id];
|
||||
},
|
||||
|
||||
/* Manipulations */
|
||||
prev: function avtm_prev() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
this.currentAVT.previous(0).then(null, function(e) {
|
||||
console.log(e.description);
|
||||
});
|
||||
},
|
||||
next: function avtm_next() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
this.currentAVT.next(0).then(null, function(e) {
|
||||
console.log(e.description);
|
||||
});
|
||||
},
|
||||
play: function avtm_play() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
this.currentAVT.play(0);
|
||||
},
|
||||
pause: function avtm_pause() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
this.currentAVT.pause(0);
|
||||
},
|
||||
stop: function avtm_stop() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
this.currentAVT.stop(0);
|
||||
},
|
||||
ff: function avtm_getPositionInfo() {
|
||||
if (!this.currentAVT) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return this.currentAVT.getPositionInfo(0).then(function(response) {
|
||||
return this.currentAVT.seek(
|
||||
'0', 'REL_TIME', this.timeDelta(response.data.RelTime, 10));
|
||||
}.bind(this));
|
||||
},
|
||||
rwd: function avtm_getPositionInfo() {
|
||||
if (!this.currentAVT) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return this.currentAVT.getPositionInfo(0).then(function(response) {
|
||||
return this.currentAVT.seek(
|
||||
'0', 'REL_TIME', this.timeDelta(response.data.RelTime, -10));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
timeDelta: function avtm_timeDelta(string, deltasec) {
|
||||
var hms = string.split(':').map(x => parseInt(x));
|
||||
var date = new Date(0, 0, 1, hms[0], hms[1], hms[2] + deltasec);
|
||||
if (date.getYear == -1) {
|
||||
return '00:00:00';
|
||||
}
|
||||
return date.getHours() + ':' +
|
||||
date.getMinutes() + ':' +
|
||||
date.getSeconds();
|
||||
},
|
||||
|
||||
getMediaInfo: function avtm_getMediaInfo() {
|
||||
if (!this.currentAVT) {
|
||||
return;
|
||||
}
|
||||
return this.currentAVT.getMediaInfo(0);
|
||||
},
|
||||
getPositionInfo: function avtm_getPositionInfo() {
|
||||
if (!this.currentAVT) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return this.currentAVT.getPositionInfo(0);
|
||||
},
|
||||
|
||||
updateTrackInfoBar: function avtm_updateTrackInfoBar() {
|
||||
this.trackInfoBar.textContent =
|
||||
'<<' + this.artist + ' - ' + this.title + '>>' +
|
||||
this.duration + '/' + (this.position ? this.position : '00:00:00');
|
||||
},
|
||||
|
||||
updateMediaInfo: function avtm_updateMediaInfo() {
|
||||
return this.getMediaInfo().then(function(response) {
|
||||
if (!response || !response.data) {
|
||||
return;
|
||||
}
|
||||
var parser = new DOMParser();
|
||||
var metatree = parser.parseFromString(
|
||||
response.data.CurrentURIMetaData, 'text/xml');
|
||||
var titleElem = metatree.getElementsByTagName('dc:title')[0];
|
||||
var artistElem = metatree.getElementsByTagName('upnp:artist')[0];
|
||||
this.title = titleElem ? titleElem.textContent : 'Unknown Title';
|
||||
this.artist = artistElem ? artistElem.textContent : 'Unknown Artist';
|
||||
this.duration = response.data.MediaDuration;
|
||||
this.updateTrackInfoBar();
|
||||
}.bind(this));
|
||||
},
|
||||
updatePositionInfo: function avtm_getMediaInfo() {
|
||||
this.getPositionInfo().then(function(response) {
|
||||
if (!response || !response.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.position = response.data.RelTime;
|
||||
this.updateTrackInfoBar();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
init: function avtm_init() {
|
||||
ServiceManager.prototype.init.apply(this);
|
||||
|
||||
this.pauseButton.addEventListener('click', this.pause.bind(this));
|
||||
this.playButton.addEventListener('click', this.play.bind(this));
|
||||
this.stopButton.addEventListener('click', this.stop.bind(this));
|
||||
|
||||
this.prevButton.addEventListener('click', this.prev.bind(this));
|
||||
this.nextButton.addEventListener('click', this.next.bind(this));
|
||||
this.ffButton.addEventListener('click', this.ff.bind(this));
|
||||
this.rewindButton.addEventListener('click', this.rwd.bind(this));
|
||||
this.avtSelector.addEventListener(
|
||||
'change', this.updateMediaInfo.bind(this));
|
||||
setInterval(this.updatePositionInfo.bind(this), 1000);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
exports.AVTManager = AVTManager;
|
||||
})(window);
|
||||
17
gaia/tv_apps/dlna-player/js/connection_service_manager.js
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
/* globals ServiceManager, Plug */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
function ConnectionServiceManager() {
|
||||
|
||||
}
|
||||
|
||||
ConnectionServiceManager.prototype = {
|
||||
__proto__: ServiceManager.prototype,
|
||||
|
||||
listElementId: 'rendererList',
|
||||
serviceType: 'upnp:urn:schemas-upnp-org:service:ConnectionManager:1',
|
||||
serviceConstructor: Plug.UPnP_ConnectionManager
|
||||
};
|
||||
exports.ConnectionServiceManager = ConnectionServiceManager;
|
||||
})(window);
|
||||
336
gaia/tv_apps/dlna-player/js/content_directory_manager.js
Executable file
|
|
@ -0,0 +1,336 @@
|
|||
/* globals Plug, deviceManager */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
//
|
||||
// TODO: Refactor to use ServiceManager as prototype.
|
||||
//
|
||||
var DEBUG = false;
|
||||
|
||||
var savedServices = {};
|
||||
|
||||
var debugEl;
|
||||
var avtSelector;
|
||||
var audioPlayer;
|
||||
var videoPlayer;
|
||||
var imagePlayer;
|
||||
var unknownPlayer;
|
||||
var discoverButton;
|
||||
var currentPlayer;
|
||||
var folderList;
|
||||
var debugPanel;
|
||||
var directoryPage;
|
||||
var toPlayerPage;
|
||||
|
||||
function debugLog(msg, level) {
|
||||
if (!DEBUG && level == 'debug') {
|
||||
return;
|
||||
}
|
||||
var logEl = document.createElement('div');
|
||||
logEl.textContent = msg;
|
||||
debugEl.appendChild(logEl);
|
||||
}
|
||||
|
||||
function toggleFolder(evt) {
|
||||
evt.preventDefault();
|
||||
var elem = evt.target;
|
||||
|
||||
if (elem.nextSibling && elem.nextSibling.tagName == 'UL') {
|
||||
// folder is opened. Close it.
|
||||
elem.parentElement.removeChild(elem.nextSibling);
|
||||
elem.classList.remove('opened');
|
||||
} else {
|
||||
// folder is closed. Open it.
|
||||
debugLog(elem.serviceId, 'debug');
|
||||
elem.classList.add('opened');
|
||||
browseFolder(
|
||||
elem.dataset.serviceId, elem.dataset.href, evt.target);
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlayer(value) {
|
||||
if (typeof value != 'boolean') {
|
||||
value = currentPlayer.classList.contains('hide');
|
||||
}
|
||||
audioPlayer.classList.add('hide');
|
||||
videoPlayer.classList.add('hide');
|
||||
imagePlayer.classList.add('hide');
|
||||
unknownPlayer.classList.add('hide');
|
||||
if (value) {
|
||||
currentPlayer.classList.remove('hide');
|
||||
}
|
||||
}
|
||||
|
||||
function togglePanel(name) {
|
||||
if (!name) {
|
||||
name = 'main';
|
||||
}
|
||||
window.location.hash = name;
|
||||
}
|
||||
|
||||
function switchPlayer(type) {
|
||||
var oldPlayer = currentPlayer;
|
||||
if (type == 'audio') {
|
||||
currentPlayer = audioPlayer;
|
||||
} else if (type == 'video') {
|
||||
currentPlayer = videoPlayer;
|
||||
} else if (type == 'image') {
|
||||
currentPlayer = imagePlayer;
|
||||
} else if (type == 'unknown') {
|
||||
currentPlayer = unknownPlayer;
|
||||
}
|
||||
if (oldPlayer != currentPlayer && currentPlayer != imagePlayer) {
|
||||
oldPlayer.src = '';
|
||||
}
|
||||
togglePlayer(true);
|
||||
togglePanel('playerPanel');
|
||||
}
|
||||
|
||||
function playFile(evt) {
|
||||
evt.preventDefault();
|
||||
var fileType = evt.target.dataset.type;
|
||||
var avtId = avtSelector.options[avtSelector.selectedIndex].value;
|
||||
if (avtId != 'local') {
|
||||
deviceManager.play(
|
||||
avtId, evt.target.dataset.href, evt.target.dataset.res);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
switchPlayer(fileType);
|
||||
|
||||
currentPlayer.hidden = false;
|
||||
currentPlayer.src = evt.target.dataset.href;
|
||||
currentPlayer.oncanplay = function() {
|
||||
this.play();
|
||||
};
|
||||
}
|
||||
|
||||
function detectTypeByMime(mimeText) {
|
||||
var type = mimeText.split('/')[0];
|
||||
var format = mimeText.split('/')[1];
|
||||
switch (type) {
|
||||
case 'audio':
|
||||
case 'video':
|
||||
case 'image':
|
||||
return type;
|
||||
case 'application':
|
||||
switch (format) {
|
||||
case 'ogg':
|
||||
return 'video';
|
||||
case 'octet-stream':
|
||||
return 'unknown';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
function wrapIntoDIDL(string) {
|
||||
string = '<DIDL-Lite xmlns:dlna="urn:schemas-dlna-org:device-1-0"' +
|
||||
' xmlns:dc="http://purl.org/dc/elements/1.1/"' +
|
||||
' xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"' +
|
||||
' xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/">' +
|
||||
string +
|
||||
'</DIDL-Lite>';
|
||||
return string;
|
||||
}
|
||||
|
||||
function browseFolder(serviceId, folderId, folderElement) {
|
||||
var mediaServer = savedServices[serviceId];
|
||||
if (!mediaServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
folderId = folderId || '';
|
||||
|
||||
mediaServer.browse(folderId).then(function(response) {
|
||||
if (!response || !response.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var data = response.data.Result;
|
||||
if (data.indexOf('xmlns:dlna') == -1) {
|
||||
data = data.replace('<DIDL-Lite ',
|
||||
'<DIDL-Lite xmlns:dlna="urn:schemas-dlna-org:device-1-0" ')
|
||||
.replace(/<unknown\>/g, '<unknown>');
|
||||
}
|
||||
var parser = new DOMParser();
|
||||
var serializer = new XMLSerializer();
|
||||
var xmlResponse = parser.parseFromString(data, 'application/xml');
|
||||
var lists = xmlResponse.documentElement.children;
|
||||
|
||||
var sublist = document.createElement('ul');
|
||||
sublist.className = 'sublist';
|
||||
|
||||
for (var i = 0; i < lists.length; i++) {
|
||||
var item = lists[i];
|
||||
|
||||
var titleElem = item.getElementsByTagName('title')[0] ||
|
||||
item.getElementsByTagName('dc:title')[0];
|
||||
var title;
|
||||
if (titleElem) {
|
||||
title = titleElem.textContent;
|
||||
}
|
||||
|
||||
var newElem;
|
||||
if (item.tagName == 'container') {
|
||||
newElem = document.createElement('li');
|
||||
|
||||
newElem.dataset.serviceId = serviceId;
|
||||
newElem.addEventListener('click', toggleFolder);
|
||||
newElem.dataset.href = item.getAttribute('id');
|
||||
newElem.textContent = title;
|
||||
newElem.className = 'folder';
|
||||
|
||||
sublist.appendChild(newElem);
|
||||
} else if (item.tagName == 'item') {
|
||||
var linkElem = item.getElementsByTagName('res')[0];
|
||||
var link, protocol, mime;
|
||||
if (linkElem) {
|
||||
link = linkElem.textContent;
|
||||
protocol = linkElem.getAttribute('protocolInfo');
|
||||
mime = protocol.split(':')[2];
|
||||
}
|
||||
var fileType = detectTypeByMime(mime);
|
||||
newElem = document.createElement('li');
|
||||
newElem.addEventListener('click', playFile);
|
||||
newElem.dataset.mime = mime;
|
||||
newElem.dataset.protocol = protocol;
|
||||
newElem.dataset.type = fileType;
|
||||
newElem.dataset.res = wrapIntoDIDL(item.outerHTML);
|
||||
newElem.dataset.href = link;
|
||||
newElem.textContent = title;
|
||||
newElem.className = fileType;
|
||||
sublist.appendChild(newElem);
|
||||
}
|
||||
}
|
||||
if (!folderElement) {
|
||||
folderList.appendChild(sublist);
|
||||
} else {
|
||||
folderElement.parentElement.
|
||||
insertBefore(sublist, folderElement.nextSibling);
|
||||
}
|
||||
|
||||
debugLog(serializer.serializeToString(xmlResponse), 'debug');
|
||||
});
|
||||
}
|
||||
|
||||
function removeSiblingList(elem) {
|
||||
if (elem.nextElementSibling.classList.contains('sublist')) {
|
||||
elem.parentElement.removeChild(elem.nextElementSibling);
|
||||
}
|
||||
}
|
||||
|
||||
function onServices(services) {
|
||||
services.addEventListener('servicefound', function servicefound(e) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (services[i].online) {
|
||||
updateService(services[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
services.addEventListener('servicelost', function servicelost(e) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (!services[i].online) {
|
||||
removeService(services[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
debugLog(services.length + ' service' +
|
||||
(services.length !== 1 ? 's' : '') +
|
||||
' found in the current network');
|
||||
|
||||
// Update services individually
|
||||
for (var j = 0; j < services.length; j++) {
|
||||
updateService(services[j]);
|
||||
}
|
||||
}
|
||||
|
||||
function removeService(service) {
|
||||
for (var savedServiceId in savedServices) {
|
||||
if (service.id === savedServiceId) {
|
||||
var serverItem = savedServices[service.id].serverItem;
|
||||
removeSiblingList(serverItem);
|
||||
serverItem.parentElement.removeChild(serverItem);
|
||||
delete savedServices[service.id];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateService(service) {
|
||||
if (!savedServices[service.id]) {
|
||||
var mediaServer =
|
||||
new Plug.UPnP_ContentDirectory(service, { debug: false });
|
||||
|
||||
savedServices[service.id] = mediaServer;
|
||||
|
||||
// Add server node
|
||||
var serverItem = document.createElement('li');
|
||||
var serverName = mediaServer.configDocument.
|
||||
getElementsByTagName('friendlyName')[0].textContent;
|
||||
serverItem.className = 'server';
|
||||
serverItem.textContent = serverName;
|
||||
serverItem.dataset.serviceId = service.id;
|
||||
folderList.appendChild(serverItem);
|
||||
|
||||
mediaServer.serverItem = serverItem;
|
||||
|
||||
browseFolder(service.id, null, serverItem);
|
||||
}
|
||||
}
|
||||
|
||||
function discover() {
|
||||
if (navigator.getNetworkServices) {
|
||||
debugLog('Searching for UPnP services in the current network...');
|
||||
navigator.getNetworkServices(
|
||||
'upnp:urn:schemas-upnp-org:service:ContentDirectory:1')
|
||||
.then(onServices, function(error) {
|
||||
debugLog('An error occurred obtaining UPnP Services [CODE: ' +
|
||||
error.code + ']');
|
||||
});
|
||||
} else {
|
||||
debugLog(
|
||||
'navigator.getNetworkServices API is not supported in this browser');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function init() {
|
||||
folderList = document.getElementById('folderList');
|
||||
debugEl = document.getElementById('debug');
|
||||
audioPlayer = document.getElementById('audioPlayer');
|
||||
videoPlayer = document.getElementById('videoPlayer');
|
||||
imagePlayer = document.getElementById('imagePlayer');
|
||||
unknownPlayer = document.getElementById('unknownPlayer');
|
||||
discoverButton = document.getElementById('discoverButton');
|
||||
avtSelector = document.getElementById('AVTList');
|
||||
directoryPage = document.getElementById('toDirectoryPage');
|
||||
toPlayerPage = document.getElementById('toPlayerPage');
|
||||
debugPanel = document.getElementById('debug');
|
||||
if (!DEBUG) {
|
||||
debugPanel.style.display = 'none';
|
||||
}
|
||||
|
||||
|
||||
currentPlayer = imagePlayer;
|
||||
togglePlayer(true);
|
||||
|
||||
discoverButton.addEventListener('click', discover);
|
||||
directoryPage.addEventListener('click', togglePanel.bind(null, ''));
|
||||
toPlayerPage.addEventListener('click',
|
||||
togglePanel.bind(null, 'playerPanel'));
|
||||
|
||||
discover();
|
||||
togglePanel();
|
||||
}
|
||||
|
||||
exports.ContentDirectoryManager = {
|
||||
init: init,
|
||||
savedServices: savedServices
|
||||
};
|
||||
})(window);
|
||||
85
gaia/tv_apps/dlna-player/js/device_manager.js
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
'use strict';
|
||||
/* global avtManager */
|
||||
|
||||
(function(exports) {
|
||||
function DeviceManager() {
|
||||
|
||||
}
|
||||
|
||||
DeviceManager.prototype = {
|
||||
devices: {},
|
||||
avtSelector: document.getElementById('AVTList'),
|
||||
|
||||
init: function dm_init() {
|
||||
return this;
|
||||
},
|
||||
|
||||
_popDeviceId: function dm_popDeviceId(serviceId) {
|
||||
return serviceId.split('::')[0];
|
||||
},
|
||||
|
||||
getDeviceByService: function dm_getDeviceByService(serviceId) {
|
||||
return this.devices[this._popDeviceId(serviceId)];
|
||||
},
|
||||
|
||||
getSelectedService: function dm_getCurrentService(serviceName) {
|
||||
var selected = this.avtSelector[this.avtSelector.selectedIndex].value;
|
||||
if (selected == 'local') {
|
||||
return null;
|
||||
} else {
|
||||
return this.getOtherService(selected, serviceName);
|
||||
}
|
||||
},
|
||||
|
||||
getOtherService: function dm_getOtherService(serviceId, otherServiceName) {
|
||||
var deviceId = this._popDeviceId(serviceId);
|
||||
for (var sid in this.devices[deviceId]) {
|
||||
if (sid.indexOf(otherServiceName) != -1) {
|
||||
return this.devices[deviceId][sid];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addService: function dm_addService(serviceWrapper) {
|
||||
var deviceId = this._popDeviceId(serviceWrapper.svc.id);
|
||||
if (!this.devices[deviceId]) {
|
||||
this.devices[deviceId] = {};
|
||||
}
|
||||
this.devices[deviceId][serviceWrapper.svc.id] = serviceWrapper;
|
||||
},
|
||||
|
||||
removeService: function dm_removeService(serviceId) {
|
||||
var deviceId = this._popDeviceId(serviceId);
|
||||
if (this.devices[deviceId]) {
|
||||
return;
|
||||
}
|
||||
delete this.devices[deviceId][serviceId];
|
||||
this.removeDeviceIfEmpty(deviceId);
|
||||
},
|
||||
|
||||
removeDeviceIfEmpty: function dm_removeIfEmpty(deviceId) {
|
||||
this.removeDevice(deviceId);
|
||||
},
|
||||
|
||||
removeDevice: function dm_removeDevice(deviceId) {
|
||||
delete this.devices[deviceId];
|
||||
},
|
||||
|
||||
play: function dm_play(serviceId, mediaUrl, mediaMetadata) {
|
||||
var deviceId = this._popDeviceId(serviceId);
|
||||
var avtService = this.devices[deviceId][serviceId];
|
||||
|
||||
avtService.stop(0).then(function(e) {
|
||||
return avtService.setAVTransportURI(0, mediaUrl, mediaMetadata);
|
||||
}).then(function(e) {
|
||||
return avtService.play(0).then(function() {
|
||||
return avtManager.updateMediaInfo();
|
||||
});
|
||||
}).then(null, function(e) {
|
||||
console.log('Error:' + e.description);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.DeviceManager = DeviceManager;
|
||||
}(window));
|
||||
12
gaia/tv_apps/dlna-player/js/dlna_player.js
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
/* globals ConnectionServiceManager, ContentDirectoryManager, AVTManager,
|
||||
DeviceManager */
|
||||
'use strict';
|
||||
|
||||
function DlnaPlayer() {
|
||||
window.connectionServiceManager = new ConnectionServiceManager().init();
|
||||
ContentDirectoryManager.init();
|
||||
window.avtManager = new AVTManager().init();
|
||||
window.deviceManager = new DeviceManager().init();
|
||||
}
|
||||
|
||||
window.dlnaPlayer = new DlnaPlayer();
|
||||
336
gaia/tv_apps/dlna-player/js/lib/plug.play/AVTransport/plug.upnp_avtransport.js
Executable file
|
|
@ -0,0 +1,336 @@
|
|||
/* Copyright (c) 2012 Opera Software ASA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A UPnP Media Renderer API for the following service types:
|
||||
*
|
||||
* - urn:schemas-upnp-org:service:AVTransport:1
|
||||
* - urn:schemas-upnp-org:service:AVTransport:2
|
||||
*
|
||||
*/
|
||||
/* jshint ignore:start */
|
||||
!(function( global, undefined ) {
|
||||
|
||||
if( !global.Plug || !global.Plug.UPnP ) {
|
||||
console.error("plug.play.js must be loaded before you can use this library");
|
||||
return;
|
||||
}
|
||||
|
||||
var allowedUPnPTypes = {
|
||||
'urn:schemas-upnp-org:service:AVTransport:1': true,
|
||||
'urn:schemas-upnp-org:service:AVTransport:2': true
|
||||
};
|
||||
|
||||
var UPnPAVTransport = function( serviceObj, opts ) {
|
||||
|
||||
Plug.UPnP.apply( this, [ serviceObj, opts ] );
|
||||
|
||||
this.upnpType = serviceObj.type;
|
||||
if( serviceObj.type.indexOf('upnp:') === 0 ) {
|
||||
this.upnpType = this.upnpType.replace('upnp:', '');
|
||||
}
|
||||
|
||||
if( !allowedUPnPTypes[ this.upnpType ] ) {
|
||||
console.error("Provided service is not a UPnP Media Renderer Service");
|
||||
return;
|
||||
}
|
||||
|
||||
// *** AVTransport:1 methods
|
||||
|
||||
this.getDeviceCapabilities = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetDeviceCapabilities', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
PlayMedia: {
|
||||
type: this.types.string
|
||||
},
|
||||
RecMedia: {
|
||||
type: this.types.string
|
||||
},
|
||||
RecQualityModes: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getMediaInfo = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetMediaInfo', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
NrTracks: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
MediaDuration: {
|
||||
type: this.types.string
|
||||
},
|
||||
CurrentURI: {
|
||||
type: this.types.string
|
||||
},
|
||||
CurrentURIMetaData: {
|
||||
type: this.types.string
|
||||
},
|
||||
NextURI: {
|
||||
type: this.types.string
|
||||
},
|
||||
NextURIMetaData: {
|
||||
type: this.types.string
|
||||
},
|
||||
PlayMedium: {
|
||||
type: this.types.string
|
||||
},
|
||||
RecordMedium: {
|
||||
type: this.types.string
|
||||
},
|
||||
WriteStatus: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getPositionInfo = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetPositionInfo', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
Track: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
TrackDuration: {
|
||||
type: this.types.string
|
||||
},
|
||||
TrackMetaData: {
|
||||
type: this.types.string
|
||||
},
|
||||
TrackURI: {
|
||||
type: this.types.string
|
||||
},
|
||||
RelTime: {
|
||||
type: this.types.string
|
||||
},
|
||||
AbsTime: {
|
||||
type: this.types.string
|
||||
},
|
||||
RelCount: {
|
||||
type: this.types.i4
|
||||
},
|
||||
AbsCount: {
|
||||
type: this.types.i4
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getTransportInfo = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetTransportInfo', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentTransportState: {
|
||||
type: this.types.string
|
||||
},
|
||||
CurrentTransportStatus: {
|
||||
type: this.types.string
|
||||
},
|
||||
CurrentSpeed: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getTransportSettings = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetTransportSettings', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
PlayMode: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
RecQualityMode: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setAVTransportURI = function( instanceId, currentURI, currentURIMetaData, callback ) {
|
||||
|
||||
return this.action('SetAVTransportURI', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
CurrentURI: {
|
||||
value: currentURI,
|
||||
type: this.types.string
|
||||
},
|
||||
CurrentURIMetaData: {
|
||||
value: currentURIMetaData,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.play = function( instanceId, speed, callback ) {
|
||||
|
||||
return this.action('Play', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Speed: {
|
||||
value: speed || '1',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.pause = function( instanceId, callback ) {
|
||||
|
||||
return this.action('Pause', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.stop = function( instanceId, callback ) {
|
||||
|
||||
return this.action('Stop', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.next = function( instanceId, callback ) {
|
||||
|
||||
return this.action('Next', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.previous = function( instanceId, callback ) {
|
||||
|
||||
return this.action('Previous', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.seek = function( instanceId, unit, target, callback ) {
|
||||
|
||||
return this.action('Seek', {
|
||||
request: {
|
||||
InstanceID: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Unit: {
|
||||
value: unit,
|
||||
type: this.types.string
|
||||
},
|
||||
Target: {
|
||||
value: target,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
// *** AVTransport:2 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:AVTransport:2') {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UPnPAVTransport.prototype = Object.create( Plug.UPnP.prototype );
|
||||
|
||||
// *** GLOBAL ASSIGNMENT ***
|
||||
|
||||
global.Plug.UPnP_AVTransport = UPnPAVTransport;
|
||||
|
||||
})( this );
|
||||
10
gaia/tv_apps/dlna-player/js/lib/plug.play/CHANGELOG
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
ConnectionManager/plug.upnp_connectionmanager.js
|
||||
- This file is changed by Rex Lee(http://github.com/rexboy7) to meet requriments of DLNA app in Gaia:
|
||||
- add prepareForConnection method
|
||||
|
||||
plug.upnp_contentdirectory.js:
|
||||
- This file is changed by Rex Lee(http://github.com/rexboy7) to meet requriments of DLNA app in Gaia.
|
||||
- maintain a configDocument property,
|
||||
stop suppressing exceptions,
|
||||
add mozSystem: true to XMLHttpRequest,
|
||||
and missellaneous.
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/* Copyright (c) 2012 Opera Software ASA
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* CHANGE LOG
|
||||
* - This file is changed by Rex Lee(http://github.com/rexboy7) to meet
|
||||
* requriments of DLNA app in Gaia:
|
||||
* - add prepareForConnection method
|
||||
*/
|
||||
|
||||
/**
|
||||
* A UPnP API for the following service types:
|
||||
*
|
||||
* - urn:schemas-upnp-org:service:ConnectionManager:1
|
||||
* - urn:schemas-upnp-org:service:ConnectionManager:2
|
||||
* - urn:schemas-upnp-org:service:ConnectionManager:3
|
||||
*
|
||||
*/
|
||||
/* jshint ignore:start */
|
||||
!(function( global, undefined ) {
|
||||
|
||||
if( !global.Plug || !global.Plug.UPnP ) {
|
||||
console.error("plug.play.js must be loaded before you can use this library");
|
||||
return;
|
||||
}
|
||||
|
||||
var allowedUPnPTypes = {
|
||||
'urn:schemas-upnp-org:service:ConnectionManager:1': true,
|
||||
'urn:schemas-upnp-org:service:ConnectionManager:2': true,
|
||||
'urn:schemas-upnp-org:service:ConnectionManager:3': true
|
||||
};
|
||||
|
||||
var UPnPConnectionManager = function( serviceObj, opts ) {
|
||||
|
||||
Plug.UPnP.apply( this, [ serviceObj, opts ] );
|
||||
|
||||
this.upnpType = serviceObj.type;
|
||||
if( serviceObj.type.indexOf('upnp:') === 0 ) {
|
||||
this.upnpType = this.upnpType.replace('upnp:', '');
|
||||
}
|
||||
|
||||
if( !allowedUPnPTypes[ this.upnpType ] ) {
|
||||
console.error("Provided service is not a UPnP Service");
|
||||
return;
|
||||
}
|
||||
|
||||
// *** ConnectionManager:1 methods
|
||||
this.prepareForConnection =
|
||||
function(protocol, peerCM, peerCID, direction, callback) {
|
||||
return this.action('PrepareForConnection', {
|
||||
request: {
|
||||
RemoteProtocolInfo: {
|
||||
type: this.types.string,
|
||||
value: protocol
|
||||
},
|
||||
PeerConnectionManager: {
|
||||
type: this.types.string,
|
||||
value: peerCM
|
||||
},
|
||||
PeerConnectionID: {
|
||||
type: this.types.i4,
|
||||
value: peerCID
|
||||
},
|
||||
Direction: {
|
||||
type: this.types.string,
|
||||
value: direction
|
||||
}
|
||||
},
|
||||
response: {
|
||||
ConnectionIDs: {
|
||||
type: this.types.string
|
||||
},
|
||||
AVTransportID: {
|
||||
type: this.types.i4
|
||||
},
|
||||
RcsID: {
|
||||
type: this.types.i4
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
this.getCurrentConnectionIDs = function( callback ) {
|
||||
|
||||
return this.action('GetCurrentConnectionIDs', {
|
||||
request: {},
|
||||
response: {
|
||||
ConnectionIDs: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getCurrentConnectionInfo = function( connectionId, callback ) {
|
||||
|
||||
return this.action('GetCurrentConnectionInfo', {
|
||||
request: {
|
||||
ConnectionID: {
|
||||
value: connectionId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
RcsID: {
|
||||
type: this.types.i4
|
||||
},
|
||||
AVTransportID: {
|
||||
type: this.types.i4
|
||||
},
|
||||
ProtocolInfo: {
|
||||
type: this.types.string
|
||||
},
|
||||
PeerConnectionManager: {
|
||||
type: this.types.string
|
||||
},
|
||||
PeerConnectionID: {
|
||||
type: this.types.i4
|
||||
},
|
||||
Direction: {
|
||||
type: this.types.string
|
||||
},
|
||||
Status: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
this.getProtocolInfo = function( callback ) {
|
||||
|
||||
return this.action('GetProtocolInfo', {
|
||||
request: {},
|
||||
response: {
|
||||
Source: {
|
||||
type: this.types.string
|
||||
},
|
||||
Sink: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
// *** ConnectionManager:2 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:ConnectionManager:2') {
|
||||
// TODO
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UPnPConnectionManager.prototype = Object.create( Plug.UPnP.prototype );
|
||||
|
||||
// *** GLOBAL ASSIGNMENT ***
|
||||
|
||||
global.Plug.UPnP_ConnectionManager = UPnPConnectionManager;
|
||||
|
||||
})( this );
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/* Copyright (c) 2012 Opera Software ASA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A UPnP Media Server API for the following service types:
|
||||
*
|
||||
* - urn:schemas-upnp-org:service:ContentDirectory:1
|
||||
* - urn:schemas-upnp-org:service:ContentDirectory:2
|
||||
* - urn:schemas-upnp-org:service:ContentDirectory:3
|
||||
* - urn:schemas-upnp-org:service:ContentDirectory:4
|
||||
*
|
||||
*/
|
||||
/* jshint ignore:start */
|
||||
!(function( global, undefined ) {
|
||||
|
||||
if( !global.Plug || !global.Plug.UPnP ) {
|
||||
console.error("plug.play.js must be loaded before you can use this library");
|
||||
return;
|
||||
}
|
||||
|
||||
var allowedUPnPTypes = {
|
||||
'urn:schemas-upnp-org:service:ContentDirectory:1': true,
|
||||
'urn:schemas-upnp-org:service:ContentDirectory:2': true,
|
||||
'urn:schemas-upnp-org:service:ContentDirectory:3': true,
|
||||
'urn:schemas-upnp-org:service:ContentDirectory:4': true
|
||||
};
|
||||
|
||||
var UPnPContentDirectory = function( serviceObj, opts ) {
|
||||
|
||||
Plug.UPnP.apply( this, [ serviceObj, opts ] );
|
||||
|
||||
this.upnpType = serviceObj.type;
|
||||
if( serviceObj.type.indexOf('upnp:') === 0 ) {
|
||||
this.upnpType = this.upnpType.replace('upnp:', '');
|
||||
}
|
||||
|
||||
if( !allowedUPnPTypes[ this.upnpType ] ) {
|
||||
console.error("Provided service is not a UPnP Media Server Service");
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new DOMParser();
|
||||
this.configDocument = parser.parseFromString(this.svc.config, 'text/xml');
|
||||
|
||||
// *** ContentDirectory:1 methods
|
||||
|
||||
this.getSystemUpdateId = function( callback ) {
|
||||
|
||||
return this.action('GetSystemUpdateID', {
|
||||
request: {},
|
||||
response: {
|
||||
Id: {
|
||||
type: this.types.ui4
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getSearchCapabilities = function( callback ) {
|
||||
|
||||
return this.action('GetSearchCapabilities', {
|
||||
request: {},
|
||||
response: {
|
||||
SearchCaps: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getSortCapabilities = function( callback ) {
|
||||
|
||||
return this.action('GetSortCapabilities', {
|
||||
request: {},
|
||||
response: {
|
||||
SortCaps: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
this.browse = function( objectId, browseFlag, filter, startingIndex, requestedCount, sortCriteria, callback ) {
|
||||
|
||||
return this.action('Browse', {
|
||||
request: {
|
||||
ObjectID: {
|
||||
value: objectId || '0',
|
||||
type: this.types.string
|
||||
},
|
||||
BrowseFlag: {
|
||||
value: browseFlag || 'BrowseDirectChildren',
|
||||
type: this.types.string
|
||||
},
|
||||
Filter: {
|
||||
value: filter || '*',
|
||||
type: this.types.string
|
||||
},
|
||||
StartingIndex: {
|
||||
value: startingIndex || '0',
|
||||
type: this.types.ui4
|
||||
},
|
||||
RequestedCount: {
|
||||
value: requestedCount || '100000',
|
||||
type: this.types.ui4
|
||||
},
|
||||
SortCriteria: {
|
||||
value: sortCriteria,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
Result: {
|
||||
type: this.types.string
|
||||
},
|
||||
NumberReturned: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
TotalMatches: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
UpdateID: {
|
||||
type: this.types.ui4
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.search = function( objectId, browseFlag, filter, startingIndex, requestedCount, sortCriteria, callback ) {
|
||||
|
||||
return this.action('Search', {
|
||||
request: {
|
||||
ObjectID: {
|
||||
value: objectId || '0',
|
||||
type: this.types.string
|
||||
},
|
||||
BrowseFlag: {
|
||||
value: browseFlag || 'BrowseDirectChildren',
|
||||
type: this.types.string
|
||||
},
|
||||
Filter: {
|
||||
value: filter || '*',
|
||||
type: this.types.string
|
||||
},
|
||||
StartingIndex: {
|
||||
value: startingIndex || '0',
|
||||
type: this.types.ui4
|
||||
},
|
||||
RequestedCount: {
|
||||
value: requestedCount || '100000',
|
||||
type: this.types.ui4
|
||||
},
|
||||
SortCriteria: {
|
||||
value: sortCriteria,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
Result: {
|
||||
type: this.types.string
|
||||
},
|
||||
NumberReturned: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
TotalMatches: {
|
||||
type: this.types.ui4
|
||||
},
|
||||
UpdateID: {
|
||||
type: this.types.ui4
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
|
||||
// *** ContentDirectory:2 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:2') {
|
||||
|
||||
// TODO: GetFeatureList, GetSortExtensionCapabilities, MoveObject
|
||||
}
|
||||
|
||||
|
||||
// *** ContentDirectory:3 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:3') {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
// *** ContentDirectory:4 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:ContentDirectory:4') {
|
||||
|
||||
// TODO
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UPnPContentDirectory.prototype = Object.create( Plug.UPnP.prototype );
|
||||
|
||||
// *** GLOBAL ASSIGNMENT ***
|
||||
|
||||
global.Plug.UPnP_ContentDirectory = UPnPContentDirectory;
|
||||
|
||||
})( this );
|
||||
|
|
@ -0,0 +1,903 @@
|
|||
/* Copyright (c) 2012 Opera Software ASA
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A UPnP Media Renderer API for the following service types:
|
||||
*
|
||||
* - urn:schemas-upnp-org:service:RenderingControl:1
|
||||
* - urn:schemas-upnp-org:service:RenderingControl:2
|
||||
* - urn:schemas-upnp-org:service:RenderingControl:3
|
||||
*
|
||||
*/
|
||||
/* jshint ignore:start */
|
||||
!(function( global, undefined ) {
|
||||
|
||||
if( !global.Plug || !global.Plug.UPnP ) {
|
||||
console.error("plug.play.js must be loaded before you can use this library");
|
||||
return;
|
||||
}
|
||||
|
||||
var allowedUPnPTypes = {
|
||||
'urn:schemas-upnp-org:service:RenderingControl:1': true,
|
||||
'urn:schemas-upnp-org:service:RenderingControl:2': true,
|
||||
'urn:schemas-upnp-org:service:RenderingControl:3': true
|
||||
};
|
||||
|
||||
var UPnPRenderingControl = function( serviceObj, opts ) {
|
||||
|
||||
Plug.UPnP.apply( this, [ serviceObj, opts ] );
|
||||
|
||||
this.upnpType = serviceObj.type;
|
||||
if( serviceObj.type.indexOf('upnp:') === 0 ) {
|
||||
this.upnpType = this.upnpType.replace('upnp:', '');
|
||||
}
|
||||
|
||||
if( !allowedUPnPTypes[ this.upnpType ] ) {
|
||||
console.error("Provided service is not a UPnP Media Renderer Service");
|
||||
return;
|
||||
}
|
||||
|
||||
// *** RenderingControl:1 methods
|
||||
|
||||
this.listPresets = function( instanceId, callback ) {
|
||||
|
||||
return this.action('ListPresets', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentPresetNameList: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.selectPreset = function( instanceId, presetName, callback ) {
|
||||
|
||||
return this.action('SelectPreset', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
PresetName: {
|
||||
value: presetName || "FactoryDefaults",
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getBrightness = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetBrightness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentBrightness: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setBrightness = function( instanceId, desiredBrightness, callback ) {
|
||||
|
||||
return this.action('SetBrightness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredBrightness: {
|
||||
value: desiredBrightness,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getContrast = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetContrast', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentContrast: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setContrast = function( instanceId, desiredContrast, callback ) {
|
||||
|
||||
return this.action('SetContrast', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredContrast: {
|
||||
value: desiredContrast,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getSharpness = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetSharpness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentSharpness: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setSharpness = function( instanceId, desiredSharpness, callback ) {
|
||||
|
||||
return this.action('SetSharpness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredSharpness: {
|
||||
value: desiredSharpness,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getRedVideoGain = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetRedVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentRedVideoGain: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setRedVideoGain = function( instanceId, desiredRedVideoGain, callback ) {
|
||||
|
||||
return this.action('SetRedVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredRedVideoGain: {
|
||||
value: desiredRedVideoGain,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getGreenVideoGain = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetGreenVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentGreenVideoGain: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setGreenVideoGain = function( instanceId, desiredGreenVideoGain, callback ) {
|
||||
|
||||
return this.action('SetGreenVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredGreenVideoGain: {
|
||||
value: desiredGreenVideoGain,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getBlueVideoGain = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetBlueVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentBlueVideoGain: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setBlueVideoGain = function( instanceId, desiredBlueVideoGain, callback ) {
|
||||
|
||||
return this.action('SetBlueVideoGain', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredBlueVideoGain: {
|
||||
value: desiredBlueVideoGain,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getRedVideoBlackLevel = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetRedVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentRedVideoBlackLevel: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setRedVideoBlackLevel = function( instanceId, desiredRedVideoBlackLevel, callback ) {
|
||||
|
||||
return this.action('SetRedVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredRedVideoBlackLevel: {
|
||||
value: desiredRedVideoBlackLevel,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getGreenVideoBlackLevel = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetGreenVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentGreenVideoBlackLevel: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setGreenVideoBlackLevel = function( instanceId, desiredGreenVideoBlackLevel, callback ) {
|
||||
|
||||
return this.action('SetGreenVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredGreenVideoBlackLevel: {
|
||||
value: desiredGreenVideoBlackLevel,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getBlueVideoBlackLevel = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetBlueVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentBlueVideoBlackLevel: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setBlueVideoBlackLevel = function( instanceId, desiredBlueVideoBlackLevel, callback ) {
|
||||
|
||||
return this.action('SetBlueVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredBlueVideoBlackLevel: {
|
||||
value: desiredBlueVideoBlackLevel,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getBlueVideoBlackLevel = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetBlueVideoBlackLevel', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentBlueVideoBlackLevel: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getColorTemperature = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetColorTemperature', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentColorTemperature: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setColorTemperature = function( instanceId, desiredColorTemperature, callback ) {
|
||||
|
||||
return this.action('SetColorTemperature', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredColorTemperature: {
|
||||
value: desiredColorTemperature,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getHorizontalKeystone = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetHorizontalKeystone', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentHorizontalKeystone: {
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setHorizontalKeystone = function( instanceId, desiredHorizontalKeystone, callback ) {
|
||||
|
||||
return this.action('SetHorizontalKeystone', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredHorizontalKeystone: {
|
||||
value: desiredHorizontalKeystone,
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getVerticalKeystone = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetVerticalKeystone', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentVerticalKeystone: {
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setVerticalKeystone = function( instanceId, desiredVerticalKeystone, callback ) {
|
||||
|
||||
return this.action('SetVerticalKeystone', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredVerticalKeystone: {
|
||||
value: desiredVerticalKeystone,
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getMute = function( instanceId, channel, callback ) {
|
||||
|
||||
return this.action('GetMute', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentMute: {
|
||||
type: this.types.boolean
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setMute = function( instanceId, channel, desiredMute, callback ) {
|
||||
|
||||
return this.action('SetMute', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
},
|
||||
DesiredMute: {
|
||||
value: desiredMute,
|
||||
type: this.types.boolean
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getVolume = function( instanceId, channel, callback ) {
|
||||
|
||||
return this.action('GetVolume', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentVolume: {
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setVolume = function( instanceId, channel, desiredVolume, callback ) {
|
||||
|
||||
return this.action('SetVolume', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
},
|
||||
DesiredVolume: {
|
||||
value: desiredVolume,
|
||||
type: this.types.ui2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getVolumeDB = function( instanceId, channel, callback ) {
|
||||
|
||||
return this.action('GetVolumeDB', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentVolume: {
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setVolumeDB = function( instanceId, channel, desiredVolume, callback ) {
|
||||
|
||||
return this.action('SetVolumeDB', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
},
|
||||
DesiredVolume: {
|
||||
value: desiredVolume,
|
||||
type: this.types.i2
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getVolumeDBRange = function( instanceId, channel, callback ) {
|
||||
|
||||
return this.action('GetVolumeDBRange', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
MinValue: {
|
||||
type: this.types.i2
|
||||
},
|
||||
MaxValue: {
|
||||
type: this.types.i2
|
||||
},
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getLoudness = function( instanceId, channel, callback ) {
|
||||
|
||||
return this.action('GetLoudness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentLoudness: {
|
||||
type: this.types.boolean
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setLoudness = function( instanceId, channel, desiredLoudness, callback ) {
|
||||
|
||||
return this.action('SetLoudness', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
Channel: {
|
||||
value: channel || 'Master',
|
||||
type: this.types.string
|
||||
},
|
||||
DesiredLoudness: {
|
||||
value: desiredLoudness,
|
||||
type: this.types.boolean
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
// *** RenderingControl:2 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:RenderingControl:2' ||
|
||||
this.upnpType === 'urn:schemas-upnp-org:service:RenderingControl:3') {
|
||||
|
||||
this.getStateVariables = function( instanceId, stateVariableList, callback ) {
|
||||
|
||||
return this.action('GetStateVariables', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
StateVariableList: {
|
||||
value: stateVariableList,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
StateVariableValuePairs: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setStateVariables = function( instanceId, renderingControlUDN, serviceType, serviceId, stateVariableValuePairs, callback ) {
|
||||
|
||||
return this.action('SetStateVariables', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
RenderingControlUDN: {
|
||||
value: renderingControlUDN,
|
||||
type: this.types.string
|
||||
},
|
||||
ServiceType: {
|
||||
value: serviceType,
|
||||
type: this.types.string
|
||||
},
|
||||
ServiceId: {
|
||||
value: serviceId,
|
||||
type: this.types.string
|
||||
},
|
||||
StateVariableValuePairs: {
|
||||
value: stateVariableValuePairs,
|
||||
type: this.types.string
|
||||
}
|
||||
},
|
||||
response: {
|
||||
StateVariableList: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// *** RenderingControl:3 methods
|
||||
|
||||
if(this.upnpType === 'urn:schemas-upnp-org:service:RenderingControl:3') {
|
||||
|
||||
this.getAllowedTransforms = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetAllowedTransforms', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentAllowedTransformSettings: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getTransforms = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetTransforms', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
}
|
||||
},
|
||||
response: {
|
||||
CurrentTransformValues: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setTransforms = function( instanceId, desiredTransformValues, callback ) {
|
||||
|
||||
return this.action('SetTransforms', {
|
||||
request: {
|
||||
InstanceId: {
|
||||
value: instanceId,
|
||||
type: this.types.ui4
|
||||
},
|
||||
DesiredTransformValues: {
|
||||
value: desiredTransformValues,
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getAllAvailableTransforms = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetAllAvailableTransforms', {
|
||||
response: {
|
||||
AllAllowedTransformSettings: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getAllowedDefaultTransforms = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetAllowedDefaultTransforms', {
|
||||
response: {
|
||||
AllowedDefaultTransformSettings: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.getDefaultTransforms = function( instanceId, callback ) {
|
||||
|
||||
return this.action('GetDefaultTransforms', {
|
||||
response: {
|
||||
CurrentDefaultTransformSettings: {
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
this.setDefaultTransforms = function( instanceId, desiredDefaultTransformSettings, callback ) {
|
||||
|
||||
return this.action('SetDefaultTransforms', {
|
||||
request: {
|
||||
DesiredDefaultTransformSettings: {
|
||||
value: desiredDefaultTransformSettings,
|
||||
type: this.types.string
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
UPnPRenderingControl.prototype = Object.create( Plug.UPnP.prototype );
|
||||
|
||||
// *** GLOBAL ASSIGNMENT ***
|
||||
|
||||
global.Plug.UPnP_RenderingControl = UPnPRenderingControl;
|
||||
|
||||
})( this );
|
||||
1056
gaia/tv_apps/dlna-player/js/lib/plug.play/plug.upnp.js
Executable file
521
gaia/tv_apps/dlna-player/js/lib/ssdp/ssdp.js
Executable file
|
|
@ -0,0 +1,521 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* jshint ignore:start */
|
||||
(function(global) {
|
||||
// Spec information:
|
||||
// http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
|
||||
|
||||
const SSDP_PORT = 1900;
|
||||
const SSDP_ADDRESS = "239.255.255.250";
|
||||
const SSDP_DISCOVER_MX = 2;
|
||||
|
||||
const SSDP_DISCOVER_PACKET =
|
||||
"M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: " + SSDP_ADDRESS + ":" + SSDP_PORT + "\r\n" +
|
||||
"MAN: \"ssdp:discover\"\r\n" +
|
||||
"MX: " + SSDP_DISCOVER_MX + "\r\n" +
|
||||
"ST: %SEARCH_TARGET%\r\n\r\n";
|
||||
|
||||
const SSDP_RESPONSE_HEADER = /HTTP\/\d{1}\.\d{1} \d+ .*/;
|
||||
const SSDP_HEADER = /^([^:]+):\s*(.*)$/;
|
||||
|
||||
var SimpleServiceDiscovery = {
|
||||
_targets: [],
|
||||
_commands: {},
|
||||
_searching: false,
|
||||
search: function _search(aInterval) {
|
||||
aInterval = aInterval || 0;
|
||||
if (aInterval > 0) {
|
||||
this._searchRepeat = setInterval(this._search.bind(this), aInterval);
|
||||
}
|
||||
this._search();
|
||||
},
|
||||
stopSearch: function _stopSearch() {
|
||||
if (this._searchRepeat) {
|
||||
clearInterval(this._searchRepeat);
|
||||
}
|
||||
},
|
||||
registerTarget: function _registerTarget(target) {
|
||||
if (this._targets.indexOf(target) < 0) {
|
||||
this._targets.push(target);
|
||||
}
|
||||
},
|
||||
|
||||
// internal function
|
||||
_search: function _search() {
|
||||
// create socket if not exist
|
||||
if (!this._searchSocket) {
|
||||
this._searchSocket = new UDPSocket({loopback: true, localPort: SSDP_PORT});
|
||||
this._searchSocket.joinMulticastGroup(SSDP_ADDRESS);
|
||||
this._searchSocket.onmessage = this._onmessage.bind(this);
|
||||
window.addEventListener('beforeunload', function(evt) {
|
||||
this._searchSocket.close();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this._searchSocket.opened.then((function() {
|
||||
// Perform a UDP broadcast to search for SSDP devices
|
||||
this._searchTimeout = setTimeout(this._searchShutdown.bind(this), SSDP_DISCOVER_MX * 1000);
|
||||
this._searching = true;
|
||||
|
||||
var data = SSDP_DISCOVER_PACKET;
|
||||
this._targets.forEach((function(target) {
|
||||
var msgData = data.replace("%SEARCH_TARGET%", target);
|
||||
var ok = this._searchSocket.send(msgData, SSDP_ADDRESS, SSDP_PORT);
|
||||
}).bind(this));
|
||||
}).bind(this)).catch(function(evt) {
|
||||
});
|
||||
},
|
||||
_searchShutdown: function _searchShutdown() {
|
||||
this._searching = false;
|
||||
},
|
||||
_onmessage: function _onmessage(e) {
|
||||
// Listen for responses from specific targets. There could be more than
|
||||
// one available.
|
||||
|
||||
var msg = String.fromCharCode.apply(null, new Uint8Array(e.data));
|
||||
var lines = msg.toString().split("\r\n");
|
||||
var firstLine = lines.shift();
|
||||
var method = SSDP_RESPONSE_HEADER.test(firstLine) ? 'RESPONSE'
|
||||
: firstLine.split(' ')[0].toUpperCase();
|
||||
var headers = {};
|
||||
lines.forEach(function (line) {
|
||||
if (line.length) {
|
||||
var pairs = line.match(/^([^:]+):\s*(.*)$/);
|
||||
if (pairs) {
|
||||
headers[pairs[1].toLowerCase()] = pairs[2];
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this._commands[method]) {
|
||||
this._commands[method].apply(this, [headers]);
|
||||
}
|
||||
},
|
||||
_found: function _found(aService) {
|
||||
// Use the REST api to request more information about this service
|
||||
var xhr = new XMLHttpRequest({mozSystem: true});
|
||||
xhr.open("GET", aService.location, true);
|
||||
xhr.overrideMimeType("text/xml");
|
||||
|
||||
xhr.addEventListener("load", (function() {
|
||||
if (xhr.status == 200) {
|
||||
// walk through root device and all the embedded devices
|
||||
var devices = xhr.responseXML.querySelectorAll('device');
|
||||
for (var i = 0; i < devices.length; i++) {
|
||||
this._parseDescriptor(devices[i], aService.location);
|
||||
}
|
||||
}
|
||||
}).bind(this), false);
|
||||
|
||||
xhr.send(null);
|
||||
},
|
||||
_parseDescriptor: function _parseDescriptor(device, refUrl) {
|
||||
var udn = device.querySelector("UDN").innerHTML;
|
||||
|
||||
var serviceList = device.querySelector('serviceList').querySelectorAll('service');
|
||||
for (var i = 0; i < serviceList.length; i++) {
|
||||
var service = serviceList[i];
|
||||
var serviceType = service.querySelector('serviceType').innerHTML;
|
||||
var eventsUrl = this._getAbsoluteURL(service.querySelector('eventSubURL'), refUrl);
|
||||
var options = {};
|
||||
options.id = udn + '::' + serviceType;
|
||||
options.deviceId = udn;
|
||||
options.name = service.querySelector('serviceId').innerHTML;
|
||||
options.type = 'upnp:' + serviceType;
|
||||
options.url = this._getAbsoluteURL(service.querySelector('controlURL').innerHTML, refUrl);
|
||||
options.config = device.outerHTML;
|
||||
if (eventsUrl) {
|
||||
options.eventsUrl = eventsUrl.innerHTML;
|
||||
}
|
||||
options.expiryTimestamp = ''; //TODO
|
||||
serviceHelper.add(new SSDPServiceRecord(options));
|
||||
}
|
||||
},
|
||||
_getAbsoluteURL: function _getAbsoluteURL(url, refUrl) {
|
||||
if (/^https?:\/\//.test(url)) {
|
||||
return url;
|
||||
} else {
|
||||
var absURL = new URL(url, refUrl);
|
||||
return absURL.toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SimpleServiceDiscovery._commands['RESPONSE'] = function _response(headers) {
|
||||
if (this._searching && headers.location && this._targets.indexOf(headers.st) >= 0) {
|
||||
this._found(headers);
|
||||
}
|
||||
};
|
||||
SimpleServiceDiscovery._commands['NOTIFY'] = function _notify(headers) {
|
||||
switch(headers.nts) {
|
||||
case 'ssdp:alive':
|
||||
var old_searching = this._searching;
|
||||
this._searching = true;
|
||||
headers['st'] = headers.nt;
|
||||
this._commands['RESPONSE'].apply(this, [headers]);
|
||||
this._searching = old_searching;
|
||||
break;
|
||||
case 'ssdp:byebye':
|
||||
serviceHelper.remove(new SSDPServiceRecord({id: headers.usn}));
|
||||
break;
|
||||
}
|
||||
};
|
||||
SimpleServiceDiscovery._commands['M-SEARCH'] = function _msearch(headers) {
|
||||
};
|
||||
|
||||
function SSDPServiceRecord(options) {
|
||||
this.update(options);
|
||||
}
|
||||
|
||||
SSDPServiceRecord.prototype = {
|
||||
id: '',
|
||||
deviceId: '',
|
||||
name: '',
|
||||
type: '',
|
||||
url: '',
|
||||
eventsUrl: '',
|
||||
config: '',
|
||||
expiryTimestamp: '',
|
||||
update: function(options) {
|
||||
var self = this;
|
||||
['id', 'deviceId', 'name', 'type', 'url', 'config', 'expiryTimestamp'].forEach(function(option) {
|
||||
self[option] = options[option];
|
||||
});
|
||||
|
||||
if (options.eventsUrl) {
|
||||
this.eventsUrl = options.eventsUrl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// polyfill Network Service Discovery API
|
||||
function NetworkService(record) {
|
||||
this._record = record;
|
||||
//XXX need weak reference
|
||||
this._callbacks = { 'available': [], 'unavailable': [], 'notify': [] };
|
||||
}
|
||||
|
||||
NetworkService.prototype = {
|
||||
online: true,
|
||||
get id() { return this._record.id; },
|
||||
get name() { return this._record.name; },
|
||||
get type() { return this._record.type; },
|
||||
get url() { return this._record.url; },
|
||||
get config() { return this._record.config; },
|
||||
|
||||
set onavailable(callback) {
|
||||
this.addEventListener('available', callback);
|
||||
},
|
||||
|
||||
set onunavailable(callback) {
|
||||
this.addEventListener('unavailable', callback);
|
||||
},
|
||||
|
||||
set onnotify(callback) {
|
||||
this.addEventListener('notify', callback);
|
||||
},
|
||||
|
||||
// EventTarget
|
||||
addEventListener: function(type, callback, capture) {
|
||||
if (!this._callbacks.hasOwnProperty(type)) {
|
||||
return;
|
||||
}
|
||||
this._callbacks[type].push(callback);
|
||||
},
|
||||
removeEventListener: function(type, callback, capture) {
|
||||
if (!this._callbacks.hasOwnProperty(type)) {
|
||||
return;
|
||||
}
|
||||
var callbacks = this._callbacks[type];
|
||||
|
||||
var index = callbacks.indexOf(callback);
|
||||
if (index >= 0) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
},
|
||||
dispatchEvent: function(event) {
|
||||
if (this._callbacks.hasOwnProperty(event.type)) {
|
||||
var callbacks = this._callbacks[event.type];
|
||||
if (!event.detail || !event.detail.target) {
|
||||
event.detail = { target: this };
|
||||
}
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i](event);
|
||||
}
|
||||
}
|
||||
return event.cancelable === false || event.defaultPrevented === false;
|
||||
},
|
||||
};
|
||||
|
||||
function NetworkServices(types, services) {
|
||||
this._services = services || [];
|
||||
this._types = types || [];
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
this[i] = services[i];
|
||||
}
|
||||
//XXX need weak reference
|
||||
this._callbacks = { 'servicefound': [], 'servicelost': [] };
|
||||
}
|
||||
|
||||
NetworkServices.prototype = {
|
||||
_append: function(service) {
|
||||
var i = this._services.length;
|
||||
this[i] = service;
|
||||
this._services.push(service);
|
||||
},
|
||||
|
||||
servicesAvailable: 0,
|
||||
|
||||
get length() {
|
||||
return this._services.length;
|
||||
},
|
||||
|
||||
getServiceById: function(id) {
|
||||
for (var i = 0; i < this._services.length; i++) {
|
||||
if (this._services[i].id === id) {
|
||||
return this._services[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
set onservicefound(callback) {
|
||||
this.addEventListener('servicefound', callback);
|
||||
},
|
||||
|
||||
set onservicelost(callback) {
|
||||
this.addEventListener('servicelost', callback);
|
||||
},
|
||||
|
||||
// EventTarget
|
||||
addEventListener: function(type, callback) {
|
||||
if (!this._callbacks.hasOwnProperty(type)) {
|
||||
return;
|
||||
}
|
||||
this._callbacks[type].push(callback);
|
||||
},
|
||||
removeEventListener: function(type, callback, capture) {
|
||||
if (!this._callbacks.hasOwnProperty(type)) {
|
||||
return;
|
||||
}
|
||||
var callbacks = this._callbacks[type];
|
||||
|
||||
var index = callbacks.indexOf(callback);
|
||||
if (index >= 0) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
},
|
||||
dispatchEvent: function(event) {
|
||||
if (this._callbacks.hasOwnProperty(event.type)) {
|
||||
var callbacks = this._callbacks[event.type];
|
||||
if (!event.detail || !event.detail.target) {
|
||||
event.detail = {target: this};
|
||||
}
|
||||
for (var i = 0; i < callbacks.length; i++) {
|
||||
callbacks[i](event);
|
||||
}
|
||||
}
|
||||
return event.cancelable === false || event.defaultPrevented === false;
|
||||
},
|
||||
};
|
||||
|
||||
function _hasRequestedType(service, types) {
|
||||
return types.some(function(type) {
|
||||
return type === service.type;
|
||||
});
|
||||
}
|
||||
|
||||
var serviceHelper = (function() {
|
||||
return {
|
||||
add: function(record) {
|
||||
// try update existing service
|
||||
var index = availableServiceRecords.findIndex(function(activeService) {
|
||||
return activeService.id === record.id;
|
||||
});
|
||||
|
||||
if (index >= 0) {
|
||||
availableServiceRecords[index].update(record);
|
||||
return;
|
||||
}
|
||||
|
||||
// new service available
|
||||
availableServiceRecords.push(record);
|
||||
|
||||
activeServiceManagers.forEach(function(manager) {
|
||||
// wiring to existed NetworkService objects
|
||||
var index = manager._services.findIndex(function(service) {
|
||||
return service.id === record.id;
|
||||
});
|
||||
if (index >= 0 && manager._services[index].online === false) {
|
||||
var service = manager._services[index];
|
||||
service._record = record;
|
||||
service.online = true;
|
||||
setTimeout(function() {
|
||||
service.dispatchEvent(new CustomEvent('available',
|
||||
{ bubbles: false,
|
||||
cancelable: false,
|
||||
detail: {target: service},
|
||||
}));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// notify correspoding service managers
|
||||
if (_hasRequestedType(record, manager._types)) {
|
||||
// XXX should we append new service to existing manager?
|
||||
if (index < 0) {
|
||||
var netService = new NetworkService(record);
|
||||
manager._append(netService);
|
||||
}
|
||||
manager.servicesAvailable++;
|
||||
setTimeout(function() {
|
||||
manager.dispatchEvent(new CustomEvent('servicefound',
|
||||
{ bubbles: false,
|
||||
cancelable: false,
|
||||
detail: {target: manager},
|
||||
}));
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
remove: function(record) {
|
||||
var index = availableServiceRecords.findIndex(function(activeService) {
|
||||
return activeService.id === record.id;
|
||||
});
|
||||
|
||||
if (index >= 0) {
|
||||
var activeService = availableServiceRecords[index];
|
||||
|
||||
activeServiceManagers.forEach(function(manager) {
|
||||
var index = manager._services.findIndex(function(service) {
|
||||
return service.id === record.id;
|
||||
});
|
||||
if (index >= 0 && manager._services[index].online === true) {
|
||||
// notify service unavailable
|
||||
var service = manager._services[index];
|
||||
service.online = false;
|
||||
setTimeout(function() {
|
||||
service.dispatchEvent(new CustomEvent('unavailable',
|
||||
{ bubbles: false,
|
||||
cancelable: false,
|
||||
detail: {target: service},
|
||||
}));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// notify service lost
|
||||
if (_hasRequestedType(activeService, manager._types)) {
|
||||
manager.servicesAvailable--;
|
||||
setTimeout(function() {
|
||||
manager.dispatchEvent(new CustomEvent('servicelost',
|
||||
{ bubbles: false,
|
||||
cancelable: false,
|
||||
detail: {target: manager},
|
||||
}));
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
// XXX unregister UPnP event
|
||||
if (activeService.eventsUrl) {
|
||||
console.log('unregister event for ' + activeService.eventsUrl);
|
||||
}
|
||||
|
||||
// remove from available service list
|
||||
availableServiceRecords.splice(index, 1);
|
||||
}
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
var availableServiceRecords = []; // list of NetworkService
|
||||
var activeServiceManagers = []; // list of NetworkServices //XXX need weak reference
|
||||
var targetRegister = {
|
||||
'upnp:': {
|
||||
register: function(target) {
|
||||
SimpleServiceDiscovery.registerTarget(target.substr(5)); // remove upnp: prefix
|
||||
},
|
||||
search: function() {
|
||||
SimpleServiceDiscovery.search();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function getNetworkServices(type) {
|
||||
const UNKNOWN_TYPE_PREFIX_ERR = 'UnknownTypePrefixError';
|
||||
const PERMISSION_DENIED_ERR = 'PermissionDeniedError';
|
||||
|
||||
function _isValidServiceType (type) {
|
||||
return type && Object.keys(targetRegister).some(function(prefix) { return type.startsWith(prefix); });
|
||||
}
|
||||
|
||||
function _register(types) {
|
||||
var needSearch = {};
|
||||
types.forEach(function(type) {
|
||||
var found = Object.keys(targetRegister).find(function(prefix) { return type.startsWith(prefix); });
|
||||
if (found) {
|
||||
targetRegister[found].register(type);
|
||||
if (!needSearch.hasOwnProperty(found)) {
|
||||
needSearch[found] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
// trigger search on the discovery service if new search target
|
||||
// is registered
|
||||
Object.keys(needSearch).forEach(function(type) {
|
||||
targetRegister[type].search();
|
||||
});
|
||||
}
|
||||
|
||||
var requestedControlTypes = [].concat(type).filter(_isValidServiceType);
|
||||
|
||||
if (requestedControlTypes.length === 0) {
|
||||
return Promise.reject(new DOMError(UNKNOWN_TYPE_PREFIX_ERR));
|
||||
}
|
||||
|
||||
_register(requestedControlTypes);
|
||||
|
||||
var allPromises = [];
|
||||
availableServiceRecords.forEach(function(service) {
|
||||
if (_hasRequestedType(service, requestedControlTypes)) {
|
||||
// XXX doesn't check CORS yet
|
||||
// create a new instance of NetworkService
|
||||
allPromises.push(Promise.resolve(new NetworkService(service)));
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(allPromises).then(function(servicesFound) {
|
||||
var services = servicesFound.filter(function(service) {
|
||||
//XXX might also filter by user preference
|
||||
return service !== null;
|
||||
});
|
||||
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (services[i]._record.eventsUrl) {
|
||||
//XXX perform UPnP event subscription
|
||||
console.log('register for ' + services[i]._record.eventsUrl);
|
||||
}
|
||||
}
|
||||
|
||||
var serviceManager = new NetworkServices(requestedControlTypes, services);
|
||||
serviceManager.servicesAvailable = availableServiceRecords.reduce(function(count, service) {
|
||||
return (_hasRequestedType(service, requestedControlTypes)) ? count + 1 : count;
|
||||
}, 0);
|
||||
activeServiceManagers.push(serviceManager);
|
||||
|
||||
return Promise.resolve(serviceManager);
|
||||
});
|
||||
}
|
||||
|
||||
// export NetworkServices and NetworkServices as pre-defined types
|
||||
global.NetworkService = NetworkService;
|
||||
global.NetworkServices = NetworkServices;
|
||||
navigator.getNetworkServices = getNetworkServices;
|
||||
|
||||
global.addEventListener('unload', function() {
|
||||
delete navigator.getNetworkServices;
|
||||
});
|
||||
})(window);
|
||||
145
gaia/tv_apps/dlna-player/js/service_manager.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
/* globals Plug, deviceManager */
|
||||
'use strict';
|
||||
|
||||
(function(exports) {
|
||||
var DEBUG = false;
|
||||
function ServiceManager() {
|
||||
|
||||
}
|
||||
|
||||
ServiceManager.prototype = {
|
||||
listElementId: 'elementList',
|
||||
serviceName: 'ConnectionManager',
|
||||
serviceType: 'upnp:urn:schemas-upnp-org:service:ConnectionManager:1',
|
||||
serviceConstructor: Plug.UPnP_ContentDirectory,
|
||||
savedServices: {},
|
||||
|
||||
init: function sm_init() {
|
||||
this.debugEl = document.getElementById('debug');
|
||||
this.listElement = document.getElementById(this.listElementId);
|
||||
this.discover();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
debugLog: function sm_debugLog(msg, level) {
|
||||
if (!DEBUG && level == 'debug') {
|
||||
return;
|
||||
}
|
||||
var logEl = document.createElement('div');
|
||||
logEl.textContent = msg;
|
||||
this.debugEl.appendChild(logEl);
|
||||
},
|
||||
|
||||
appendMixinProperties: function sm_appendConfigDocument(serviceWrapper) {
|
||||
var parser = new DOMParser();
|
||||
serviceWrapper.configDocument =
|
||||
parser.parseFromString(serviceWrapper.svc.config, 'text/xml');
|
||||
serviceWrapper.friendlyName = serviceWrapper.configDocument.
|
||||
getElementsByTagName('friendlyName')[0].textContent;
|
||||
},
|
||||
|
||||
updateService: function sm_updateService(service) {
|
||||
if (!this.savedServices[service.id]) {
|
||||
var mediaServer =
|
||||
new this.serviceConstructor(service, { debug: false });
|
||||
this.appendMixinProperties(mediaServer);
|
||||
this.savedServices[service.id] = mediaServer;
|
||||
deviceManager.addService(mediaServer);
|
||||
|
||||
var serverItem = this.serverView(mediaServer);
|
||||
this.listElement.appendChild(serverItem);
|
||||
mediaServer.serverItem = serverItem;
|
||||
}
|
||||
},
|
||||
|
||||
removeService: function sm_removeService(service) {
|
||||
for(var id in this.savedServices) {
|
||||
if (service.id === id) {
|
||||
var serverItem = this.savedServices[service.id].serverItem;
|
||||
this.removeSiblingList(serverItem);
|
||||
serverItem.parentElement.removeChild(serverItem);
|
||||
delete this.savedServices[service.id];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
serverView: function sm_serverView(serviceWrapper) {
|
||||
// Add server node
|
||||
var serverItem = document.createElement('li');
|
||||
var serverName = serviceWrapper.friendlyName;
|
||||
serverItem.className = 'server';
|
||||
serverItem.textContent = serverName;
|
||||
serverItem.dataset.serviceId = serviceWrapper.svc.id;
|
||||
return serverItem;
|
||||
},
|
||||
|
||||
onServices: function sm_onServices(services) {
|
||||
services.addEventListener('servicefound', function servicefound(e) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (services[i].online) {
|
||||
this.updateService(services[i]);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
services.addEventListener('servicelost', function servicelost(e) {
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (!services[i].online) {
|
||||
this.removeService(services[i]);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.debugLog(services.length + ' service' +
|
||||
(services.length !== 1 ? 's' : '') +
|
||||
' found in the current network');
|
||||
|
||||
// Remove offline services
|
||||
for (var savedServiceId in this.savedServices) {
|
||||
var removed = true;
|
||||
for (var i = 0; i < services.length; i++) {
|
||||
if (services[i].id == savedServiceId) {
|
||||
removed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
this.remove(savedServiceId);
|
||||
}
|
||||
}
|
||||
|
||||
// Update services individually
|
||||
for (var j = 0; j < services.length; j++) {
|
||||
this.updateService(services[j]);
|
||||
}
|
||||
},
|
||||
|
||||
get currentService() {
|
||||
return deviceManager.getSelectedService(this.serviceName);
|
||||
},
|
||||
|
||||
|
||||
removeSiblingList: function sm_remove(elem) {
|
||||
if (elem.nextElementSibling.classList.contains('sublist')) {
|
||||
elem.parentElement.removeChild(elem.nextElementSibling);
|
||||
}
|
||||
},
|
||||
|
||||
discover: function sm_discover() {
|
||||
if (navigator.getNetworkServices) {
|
||||
this.debugLog('Searching for UPnP services in the current network...');
|
||||
|
||||
navigator.getNetworkServices(this.serviceType)
|
||||
.then(this.onServices.bind(this), function(e) {
|
||||
this.debugLog('An error occurred obtaining UPnP Services [CODE: ' +
|
||||
e.code + ']');
|
||||
});
|
||||
} else {
|
||||
this.debugLog(
|
||||
'navigator.getNetworkServices API is not supported in this browser');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.ServiceManager = ServiceManager;
|
||||
})(window);
|
||||
20
gaia/tv_apps/dlna-player/manifest.webapp
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "dlnaPlayer",
|
||||
"description": "Gaia dlna player",
|
||||
"type": "certified",
|
||||
"role": "system",
|
||||
"launch_path": "index.html",
|
||||
"developer": {
|
||||
"name": "Rex Lee, Shih-Chiang Chien",
|
||||
"url": "https://github.com/mozilla-b2g/gaia"
|
||||
},
|
||||
"permissions": {
|
||||
"systemXHR": {},
|
||||
"udp-socket": {}
|
||||
},
|
||||
"default_locale": "en-US",
|
||||
"icons": {
|
||||
"128": "/style/icons/Blank.png"
|
||||
},
|
||||
"orientation": "landscape"
|
||||
}
|
||||
79
gaia/tv_apps/dlna-player/style/app.css
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
html {
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 100%;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
section[role="region"] {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 4rem);
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: fixed;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: calc(100% - 4rem);
|
||||
background-color: #FFFFFF;
|
||||
transition: transform ease 0.4s;
|
||||
}
|
||||
|
||||
#contentList {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - 9rem);
|
||||
top: 5rem;
|
||||
bottom: 0rem;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
#trackInfoBar {
|
||||
float: right;
|
||||
overflow: hidden;
|
||||
color: #E6E6E3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: calc(100% - 11.5rem);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel:target {
|
||||
transform: translateX(0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
section[role="region"] [role="tablist"] {
|
||||
position: absolute;
|
||||
background-color: #212121;
|
||||
}
|
||||
|
||||
#controlButtons[role="tablist"] li,
|
||||
section[role="region"] [role="tablist"] li {
|
||||
background-color: #454545;
|
||||
}
|
||||
|
||||
#controlButtons[role="tablist"] button {
|
||||
width: calc(100% / 7 - 0.5rem);
|
||||
}
|
||||
|
||||
[role="tablist"] button {
|
||||
font-size: 1.5rem;
|
||||
color: #919191;
|
||||
font-style: normal;
|
||||
}
|
||||
[role="tablist"] {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
}
|
||||
BIN
gaia/tv_apps/dlna-player/style/icons/Blank.png
Executable file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
gaia/tv_apps/dlna-player/style/images/folder_closed.png
Executable file
|
After Width: | Height: | Size: 185 B |
BIN
gaia/tv_apps/dlna-player/style/images/folder_opened.png
Executable file
|
After Width: | Height: | Size: 181 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_folder.png
Executable file
|
After Width: | Height: | Size: 244 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_image.png
Executable file
|
After Width: | Height: | Size: 419 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_music.png
Executable file
|
After Width: | Height: | Size: 190 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_server.png
Executable file
|
After Width: | Height: | Size: 207 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_unknown.png
Executable file
|
After Width: | Height: | Size: 400 B |
BIN
gaia/tv_apps/dlna-player/style/images/icon_video.png
Executable file
|
After Width: | Height: | Size: 182 B |
BIN
gaia/tv_apps/dlna-player/style/images/player.png
Executable file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
gaia/tv_apps/dlna-player/style/images/player@1.5x.png
Executable file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
gaia/tv_apps/dlna-player/style/images/player@2x.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
gaia/tv_apps/dlna-player/style/images/playlist.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gaia/tv_apps/dlna-player/style/images/search-icon.png
Executable file
|
After Width: | Height: | Size: 489 B |
BIN
gaia/tv_apps/dlna-player/style/images/search-icon@1.5x.png
Executable file
|
After Width: | Height: | Size: 706 B |
BIN
gaia/tv_apps/dlna-player/style/images/search-icon@2x.png
Executable file
|
After Width: | Height: | Size: 1,010 B |
86
gaia/tv_apps/dlna-player/style/service_manager.css
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
#folderList a,
|
||||
#folderList div {
|
||||
padding-top: 3px;
|
||||
}
|
||||
#debug {
|
||||
padding: 0.5em;
|
||||
border: thin inset;
|
||||
font: small monospace;
|
||||
color: black;
|
||||
}
|
||||
.audio, .video, .image, .unknown, .server, .folder, #folderlist a {
|
||||
display: block;
|
||||
padding-top: 3px;
|
||||
padding-left: 20px;
|
||||
height: 5rem;
|
||||
line-height: 5rem;
|
||||
width: auto;
|
||||
border-bottom: 0.1rem solid #E6E6E3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
li.folder {
|
||||
color: #FF9900;
|
||||
padding-left: 40px;
|
||||
background: url(images/folder_closed.png) left center no-repeat,
|
||||
url(images/icon_folder.png) left 16px center no-repeat;
|
||||
}
|
||||
li.folder.opened {
|
||||
background: url(images/folder_opened.png) left center no-repeat,
|
||||
url(images/icon_folder.png) left 16px center no-repeat;
|
||||
}
|
||||
.audio {
|
||||
color: #00CC66;
|
||||
background: url(images/icon_music.png) left center no-repeat;
|
||||
}
|
||||
.video {
|
||||
color: #00CC66;
|
||||
background: url(images/icon_video.png) left center no-repeat;
|
||||
}
|
||||
.image {
|
||||
color: #00CC66;
|
||||
background: url(images/icon_image.png) left center no-repeat;
|
||||
}
|
||||
.unknown {
|
||||
color: #00CC66;
|
||||
background: url(images/icon_unknown.png) left center no-repeat;
|
||||
}
|
||||
.server {
|
||||
background: url(images/icon_server.png) left center no-repeat;
|
||||
font-weight: bold;
|
||||
}
|
||||
.player {
|
||||
position: absolute;
|
||||
bottom: 4rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: calc(100% - 9rem);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
color: #003399;
|
||||
}
|
||||
li:visited {
|
||||
color: #999999;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
.needRefresh {
|
||||
cursor: pointer;
|
||||
padding-left: 40px;
|
||||
color: #00CC66;
|
||||
}
|
||||
|
||||
#discoverButton {
|
||||
background: url(images/search-icon.png) center center / 30px no-repeat;
|
||||
}
|
||||
#toPlayerPage {
|
||||
background: url(images/player.png) center center / 30px no-repeat;
|
||||
}
|
||||
#toDirectoryPage {
|
||||
background: url(images/playlist.png) center center / 16px no-repeat;
|
||||
}
|
||||
|
||||