$(document).ready(function() { var $container = $('#video-container'), $searchForm = $('#video-search'), $videoResults = $('#video-results'), $volumeCtrl = $('#video-volume-ctrl'), $ctrlForm = $('#video-ctrl'), $devsPanel = $('#media-devices-panel'), $devsList = $devsPanel.find('.devices-list'), $devsBtn = $('button[data-panel="#media-devices-panel"]'), $devsBtnIcon = $('#media-devices-panel-icon'), $devsRefreshBtn = $devsPanel.find('.refresh-devices'), $searchBarContainer = $('#media-search-bar-container'), $mediaBtnsContainer = $('#media-btns-container'), $mediaItemPanel = $('#media-item-panel'), prevVolume = undefined, selectedResource = undefined; var updateVideoResults = function(videos) { $videoResults.html(''); for (var video of videos) { var $videoResult = $('
') .addClass('video-result') .attr('data-url', video['url']) .attr('data-panel', '#media-item-panel') .html('title' in video ? video['title'] : video['url']); var $icon = getVideoIconByUrl(video['url']); $icon.prependTo($videoResult); $videoResult.appendTo($videoResults); } }; var getVideoIconByUrl = function(url) { var $icon = $(''); if (url.startsWith('file://')) { $icon.addClass('fa-download'); } else if (url.startsWith('https://www.youtube.com/')) { $icon.addClass('fa-youtube'); } else if (url.startsWith('magnet:?')) { $icon.addClass('fa-film'); } else { $icon.addClass('fa-video'); } var $iconContainer = $('').addClass('video-icon-container'); $icon.appendTo($iconContainer); return $iconContainer; }; var getSelectedDevice = function() { var device = { isBrowser: false, isRemote: false, name: undefined }; var $remoteDevice = $devsPanel.find('.cast-device.selected') .filter((i, dev) => !$(dev).data('local') && !$(dev).data('browser') && $(dev).data('name')); var $browserDevice = $devsPanel.find('.cast-device.selected') .filter((i, dev) => $(dev).data('browser')); if ($remoteDevice.length) { device.isRemote = true; device.name = $remoteDevice.data('name'); } else if ($browserDevice.length) { device.isBrowser = true; } return device; }; var startStreamingTorrent = function(torrent) { return new Promise((resolve, reject) => { execute( { type: 'request', action: 'media.webtorrent.play', args: { resource: torrent, download_only: true, } }, (response) => { resolve(response.response.output.url); }, (error) => { reject(error); } ); }); }; var startStreaming = function(media) { if (media.startsWith('magnet:?')) { return new Promise((resolve, reject) => { startStreamingTorrent(media) .then((url) => { resolve(url); }) .catch((error) => { reject(error); }); }); } return new Promise((resolve, reject) => { $.ajax({ type: 'PUT', url: '/media', contentType: 'application/json', data: JSON.stringify({ source: media }), complete: (xhr, textStatus) => { var url; if (xhr.status == 200) { url = xhr.responseJSON.url; } else if (xhr.status == 409) { // Media mount point already registered url = xhr.responseText.match( /.*is already registered on ("|")(https?:\/\/[^\/]+\/media\/[0-9a-f]+\.[0-9a-z]+)("|").*/)[2] } if (url) { var uri = url.match(/https?:\/\/[^\/]+(\/media\/.*)/)[1] resolve(uri); } else { reject(Error(xhr.responseText)); } }, }); }); }; var stopStreaming = function(media_id) { return new Promise((resolve, reject) => { $.ajax({ type: 'DELETE', url: '/media/' + media_id, contentType: 'application/json', }); }); }; var play = function(resource) { return new Promise((resolve, reject) => { var results = $videoResults.html(); var onVideoLoading = function() { $videoResults.text('Loading video...'); }; var onVideoReady = function() { $videoResults.html(results); }; var requestArgs = { type: 'request', action: 'media.play', args: { resource: resource }, }; var selectedDevice = getSelectedDevice(); if (selectedDevice.isBrowser) { onVideoLoading(); startStreaming(resource) .then((url) => { window.open(url, '_blank'); resolve(url); }) .catch((xhr, status, error) => { reject(xhr.responseText); }) .finally(() => { onVideoReady(); }); return; } if (selectedDevice.isRemote) { requestArgs.action = 'media.chromecast.play'; requestArgs.args.chromecast = selectedDevice.name; } onVideoLoading(); execute( requestArgs, function(response) { $videoResults.html(results); resolve(resource); }, function(xhr, status, error) { onVideoReady(); reject(xhr.responseText); } ); }); }; var download = function(resource) { // TODO }; var initBindings = function() { $searchForm.on('submit', function(event) { var $input = $(this).find('input[name=video-search-text]'); var resource = $input.val(); var request = {} var onSuccess = function() {}; var onError = function() {}; var onComplete = function() { $input.prop('disabled', false); }; $input.prop('disabled', true); $videoResults.text('Searching...'); if (resource.match(new RegExp('^https?://')) || resource.match(new RegExp('^file://'))) { var videos = [{ url: resource }]; updateVideoResults(videos); request = { type: 'request', action: 'media.play', args: { resource: resource } }; } else { request = { type: 'request', action: 'media.search', args: { query: resource } }; onSuccess = function(response) { var videos = response.response.output; updateVideoResults(videos); }; } execute(request, onSuccess, onError, onComplete) return false; }); $ctrlForm.on('submit', function() { return false; }); $ctrlForm.find('button[data-action]').on('click touch', function(evt) { var action = $(this).data('action'); var $btn = $(this); var requestArgs = { type: 'request', action: 'media.' + action, }; var selectedDevice = getSelectedDevice(); if (selectedDevice.isBrowser) { return; // The in-browser player can be used to control media } if (selectedDevice.isRemote) { requestArgs.action = 'media.chromecast.' + action; requestArgs.args = { 'chromecast': selectedDevice.name }; } execute(requestArgs); }); $volumeCtrl.on('mousedown touchstart', function(event) { prevVolume = $(this).val(); }); $volumeCtrl.on('mouseup touchend', function(event) { var requestArgs = { type: 'request', action: 'media.set_volume', args: { volume: $(this).val() }, }; var selectedDevice = getSelectedDevice(); if (selectedDevice.isRemote) { requestArgs.action = 'media.chromecast.set_volume', requestArgs.args.chromecast = selectedDevice.name; } execute(requestArgs, onSuccess=undefined, onError = function() { $volumeCtrl.val(prevVolume); } ); }); $videoResults.on('mousedown touchstart', '.video-result', function() { selectedResource = $(this).data('url'); }); $videoResults.on('mouseup touchend', '.video-result', function(event) { var $item = $(this); var resource = $item.data('url'); if (resource !== selectedResource) { return; // Did not really click this item } $item.siblings().removeClass('selected'); $item.addClass('selected'); $mediaItemPanel.css('top', (event.clientY + $(window).scrollTop()) + 'px'); $mediaItemPanel.css('left', (event.clientX + $(window).scrollLeft()) + 'px'); $mediaItemPanel.data('resource', resource); }); $devsBtn.on('click touch', function() { $(this).toggleClass('selected'); $devsPanel.css('top', ($(this).position().top + $(this).outerHeight()) + 'px'); $devsPanel.css('left', ($(this).position().left) + 'px'); return false; }); $devsPanel.on('mouseup touchend', '.cast-device', function() { if ($(this).hasClass('disabled')) { return; } var $devices = $devsPanel.find('.cast-device'); var $curSelected = $devices.filter((i, d) => $(d).hasClass('selected')); if ($curSelected.data('name') !== $(this).data('name')) { $curSelected.removeClass('selected'); $(this).addClass('selected'); $devsBtnIcon.attr('class', $(this).find('.fa').attr('class')); if ($(this).data('browser') || $(this).data('local')) { $devsBtn.removeClass('remote'); } else { $devsBtn.addClass('remote'); } // TODO Logic for switching destination on the fly } $devsPanel.hide(); $devsBtn.removeClass('selected'); }); $devsRefreshBtn.on('click', function() { if ($(this).hasClass('disabled')) { return; } $(this).addClass('disabled'); initRemoteDevices(); }); $mediaItemPanel.on('click', '[data-action]', function() { if ($(this).hasClass('disabled')) { return; } var action = $(this).data('action'); var resource = $mediaItemPanel.data('resource'); if (!resource) { return; } $mediaItemPanel.hide(); $mediaItemPanel.find('[data-action]').addClass('disabled'); eval(action)($mediaItemPanel.data('resource')) .finally(() => { $mediaItemPanel.find('[data-action]').removeClass('disabled'); }); }); }; var initRemoteDevices = function() { $devsList.find('.cast-device[data-remote]').addClass('disabled'); execute( { type: 'request', action: 'media.chromecast.get_chromecasts', }, function(results) { $devsRefreshBtn.removeClass('disabled'); $devsList.find('.cast-device[data-remote]').remove(); if (!results || results.response.errors.length) { return; } results = results.response.output; for (var cast of results) { var $cast = $('
').addClass('row cast-device') .addClass('cast-device-' + cast.type).attr('data-remote', true) .data('name', cast.name); var icon = 'question'; switch (cast.type) { case 'cast': icon = 'tv'; break; case 'audio': icon = 'volume-up'; break; } var $castIcon = $('').addClass('fa fa-' + icon) .addClass('cast-device-icon'); var $castName = $('').addClass('cast-device-name') .text(cast.name); var $iconContainer = $('
').addClass('two columns'); var $nameContainer = $('
').addClass('ten columns'); $castIcon.appendTo($iconContainer); $castName.appendTo($nameContainer); $iconContainer.appendTo($cast); $nameContainer.appendTo($cast); $cast.appendTo($devsList); } } ); }; var init = function() { initRemoteDevices(); initBindings(); }; init(); });