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.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View 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">&nbsp;</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">|&#9664;</button>
</li>
<li>
<button id="remoteRewind">&#9664;&#9664;</button>
</li>
<li>
<button id="remotePlay">&#9658;</button>
</li>
<li>
<button id="remotePause">&#9616;&#9616;</button>
</li>
<li>
<button id="remoteStop">&#9724;</button>
</li>
<li>
<button id="remoteFF">&#9658;&#9658;</button>
</li>
<li>
<button id="remoteNext">&#9658;|</button>
</li>
</ul>
</body>
</html>

View 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);

View 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);

View 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, '&lt;unknown&gt;');
}
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);

View 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));

View 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();

View 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 );

View 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.

View file

@ -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 );

View file

@ -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 );

View file

@ -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 );

File diff suppressed because it is too large Load diff

View 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);

View 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);

View 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"
}

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,010 B

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