Skip to content

Commit

Permalink
Merge pull request jez500#118 from christianprescott/addon-youtube
Browse files Browse the repository at this point in the history
Add YouTube addon
  • Loading branch information
jez500 committed Aug 10, 2015
2 parents e93332b + 882a16b commit f2bf4e5
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@

<!-- Addons -->
<script src="js/addons/plugin.audio.soundcloud.js"></script>
<script src="js/addons/plugin.video.youtube.js"></script>
<script src="js/addons/plugin.audio.radio.js"></script>


Expand Down
215 changes: 215 additions & 0 deletions src/js/addons/plugin.video.youtube.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/***********************************************
* YouTube
***********************************************/
app.addOns.addon.pluginvideoyoutube = {
getAddon: function(){
return app.addOns.getAddon('pluginvideoyoutube');
},

matchId: function(text){
var id = null;
var regex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v\=))|youtu\.be\/)?([a-zA-Z0-9_-]{6,11})/g;
var match = regex.exec(text);
if(match){
id = match[1];
}
return id;
},

getSearchPath: function(query){
return 'plugin://plugin.video.youtube/search/?q=' + query;
},

getPlayIdPath: function(id){
return 'plugin://plugin.video.youtube/play/?video_id=' + id;
},

/**
* Hook into file dir click
* @param record
* @returns {*}
*/
clickDir: function(record){
if(app.addOns.addon.pluginvideoyoutube.isYouTube(record)){
if(record.title == 'Search'){

app.addOns.addon.pluginvideoyoutube.doSearchDialog();
}
}
return record;
},


/**
* Hook into post file row creation
* @param $el
* @param file
* @returns {*}
*/
postProcessFileView: function($el, file){
var self = app.addOns.addon.pluginvideoyoutube;
if(self.isYouTube(file)){
var yt = self.getAddon();

// if root item for addon
if(file.file == yt.file){
var $actions = $('.file-actions', $el);

// replace play and add with open and search
$actions.html(
'<button class="btn" id="youtubeOpen"><i class="icon-folder-open"></i></button>' +
'<button class="btn" id="youtubeSearch"><i class="icon-search"></i></button>'
);

$('#youtubeOpen', $actions).on('click', function(e){
e.stopPropagation();
self.doOpenDialog();
});

$('#youtubeSearch', $actions).on('click', function(e){
e.stopPropagation();
self.doSearchDialog();
});

// add class to show actions
$el.find('.file-item').addClass('show-actions');
}
}
return $el;
},

/**
* This adds youtube to the search page
* @param $el
* @param key
* @returns {*}
*/
searchAddons: function($container, key){

var $el = $('<div></div>');

var self = app.addOns.addon.pluginvideoyoutube,
$nores = $('<div>', {class: 'addon-box', id: 'yt-search'}),
$heading = $( self.searchHeading('YouTube search for: <span>' + key + '</span>', 'youtube')),
cache = self.cache('get', key, false);

// if cache
if(cache !== false){
// just set results from cached view
$el.html(new app.FilesListView({model: cache}).render().$el);
$el.prepend($heading);
} else {
// no cache, do the search
$nores.append( self.searchHeading('Search YouTube for: <span>' + key + '</span>', 'youtube can-click') );
$el.append($nores);

// click/search action
$('#yt-search', $el).on('click', function(){
// Loading
$el.html( $(self.searchHeading('Searching YouTube for: <span>' + key + '</span>', 'loading')) );
// Callback
self.getSearchResults(key, function(result){
app.cached.fileListView = new app.FilesListView({model: result});
view = app.cached.fileListView;
$el.html(view.render().$el);
// add heading
$el.prepend($heading);
// set cache
self.cache('set', key, result);
});
});
}

$('#search-addons').append($el);
},

// Heading creator
searchHeading: function(text, classes){
var icon = '<i class="fa fa-youtube-play entity-icon" style="background-color: #E5302B"></i>';
return '<h3 class="search-heading entity-heading ' + classes + '">' + icon + text + '</h3>';
},

/**
* Get and set cache, when get, data is default
* @param op
* @param key
* @param data
* @returns {*}
*/
cache:function(op, key, data){
if(typeof app.cached.youTubeSearch == 'undefined'){
app.cached.youTubeSearch = {};
}
switch (op){
case 'get':
return (typeof app.cached.youTubeSearch[key] == 'undefined' ? data : app.cached.youTubeSearch[key]);
case 'set':
app.cached.youTubeSearch[key] = data;
return app.cached.youTubeSearch[key];
}
},


/**
* Search dialog
*/
doSearchDialog: function(){
var self = app.addOns.addon.pluginvideoyoutube;
app.helpers.prompt('Search', function(text){
// set title and notify
$('#folder-name').html('Search for "' + text + '"');
var msg = 'Searching for ' + text;
// set content while loading
$('#files-container').html('<div class="loading-box">'+msg+'</div>');

self.getSearchResults(text, function(result){
app.cached.filesSearchView = new app.FilesView({"model":result}).render();
});
});
},


/**
* Open dialog
*/
doOpenDialog: function(){
var self = app.addOns.addon.pluginvideoyoutube;
app.helpers.prompt('Open link', {
"Play": function(text){
if(text.length < 1) return false;
var id = self.matchId(text);
if(id !== null)
app.VideoController.insertAndPlay('file', self.getPlayIdPath(id), function(){});
// TODO: Display an error if the video ID can't be parsed.
},
"Add": function(text){
if(text.length < 1) return false;
var id = self.matchId(text);
if(id !== null)
app.VideoController.addToPlaylist(self.getPlayIdPath(id), 'file', 'add', function(){});
}
});
},

/**
* Get a youtube search result view
* @param query
*/
getSearchResults: function(query, callback){
// get search directory
var path = app.addOns.addon.pluginvideoyoutube.getSearchPath(query);

app.cached.fileCollection = new app.FileCollection();
app.cached.fileCollection.fetch({"sourcetype": 'video', "name": path, "success": callback});
},

/**
* If youtube file record
* @param record
* @returns {*}
*/
isYouTube: function(record){
return (record.file.indexOf('plugin.video.youtube') >= 0);
}

};
24 changes: 20 additions & 4 deletions src/js/controllers/addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,39 @@ app.addOns = {addon: {}};

app.addOns.getSources = function(callback){

// @TODO make work with video addons
app.xbmcController.command('Addons.GetAddons', ['xbmc.addon.audio', 'unknown', 'all', ["name", "thumbnail", "enabled"]], function(res){
var commands = [];
var params = ['unknown', 'all', ["name", "thumbnail", "enabled", "extrainfo"]];
commands.push({method: 'Addons.GetAddons', params: ['xbmc.addon.audio'].concat(params)});
commands.push({method: 'Addons.GetAddons', params: ['xbmc.addon.video'].concat(params)});

app.xbmcController.multipleCommand(commands, function(res){
// add a title before return
var sources = res.result.addons,
var sources = res[0].result.addons.concat(res[1].result.addons),
addons = [];

// parse
for(var i in sources){
var item = sources[i], defaults = {};

// Attempt to determine content type from extrainfo
var provides = 'music';
for(var index in item.extrainfo){
// Argh, why couldn't Kodi just serve us an object?
if(item.extrainfo[index].key === 'provides'){
provides = item.extrainfo[index].value;
if(provides === 'audio') provides = 'music';
break;
}
}

if(item.enabled){
// extend
defaults = {
file: 'plugin://' + item.addonid + '/',
title: item.name,
filetype: 'directory',
id: item.addonid,
sourcetype: 'music', // @TODO make work with video addons
sourcetype: provides,
playlistId: app.AudioController.playlistId
};
item = $.extend(item, defaults);
Expand Down
35 changes: 27 additions & 8 deletions src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -728,25 +728,44 @@ $(document).ready(function(){
* Emulates prompt() but using our dialog
* @param msg
* string message to display
* @param success
* function callback
* @param handler
* function callback or map of string message to function callback
*/
app.helpers.prompt = function(msg, success){

var opts = {
title: 'Prompt',
buttons: {
app.helpers.prompt = function(msg, handler){
var buttons;
if(typeof handler !== 'object'){
// Default OK/Cancel buttons
buttons = {
"OK": function(){
var text = $('#promptText').val();
if(text !== ''){
success(text);
handler(text);
$( this ).dialog( "close" );
}
},
"Cancel": function() {
$( this ).dialog( "close" );
}
};
} else {
// User has provided their own buttons
buttons = {};
// Get the text and send to callback
var wrap = function(callback){
return function(){
if(callback($('#promptText').val()) !== false)
$( this ).dialog( "close" );
};
};
for(var label in handler){
var callback = handler[label];
buttons[label] = wrap(callback);
}
}

var opts = {
title: 'Prompt',
buttons: buttons
};

msg += '<div class="form-item"><input type="text" class="form-text" id="promptText" /></div>';
Expand Down

0 comments on commit f2bf4e5

Please sign in to comment.