diff --git a/APIv2-3_Reference.md b/APIv2-3_Reference.md new file mode 100644 index 0000000..9922bfa --- /dev/null +++ b/APIv2-3_Reference.md @@ -0,0 +1,185 @@ +#APIv2 to APIv3 Reference Notes + +Notes to reference for the conversion for a better understanding on what has changed for myself and others. + +## Youtube-TV Elements Chart + +Currently used elements in `ytv.js` and their new APIv3 counterparts. + +#### User (channel) Playlists + +playlists = | res.feed.entry +----------- | -------------- + | **res.feed.items** + +`playlists[i]` + +Element | Old Value | New Value +------- | --------- | --------- +title | .title.$t | .snippet.title +plid | .yt$playlistId.$t | .id +thumb | .media$group.media$thumbnail[1].url | .snippet.thumbnails.medium.url + +#### User (channel) Info + +user = | userInfo.entry +------ | -------------- + | **userInfo.items[0]** + +Element | Old Value | New Value +------- | --------- | --------- +title | .title.$t | .snippet.title +url | .yt$username.$t | .id +thumb | .media$thumbnail.url | .snippet.thumbnails.default.url +summary | .summary.$t | .snippet.description +subscribers | .yt$statistics.subscriberCount | .statistics.subscriberCount +views | .yt$statistics.totalUploadViews | .statistics.viewCount +**`NEW`** | - | - +uploads | n/a | .contentDetails.relatedPlaylists.uploads + +To support newer accounts by using channel ID instead of user ID. +**Old:** `url: local+'//youtube.com/user/'+userInfo.entry.yt$username.$t` +**New:** `url: 'https://youtube.com/channel/'+userInfo.id` + +#### `NEW` Playlist Videos + +playlistVideos = | n/a +---------------- | --- + | **res.feed.items** + +`plistlistVideos[i]` + +Element | Old Value | New Value +------- | --------- | --------- +slug | n/a | .contentDetails.videoId + +#### Video Info + +videos = | data.feed.entry +-------- | --------------- + | **data.feed.items** + +`videos[i]` + +Element | Old Value | New Value +------- | --------- | --------- +title | .title.$t | .snippet.title +*slug | .media$group.yt$videoid.$t | .id +link | .link[0].href | n/a *use slug +published | .published.$t | .snippet.publishedAt +rating | .yt$rating | n/a *see statistics +stats | .yt$statistics | .statistics +duration | ( .media$group.yt$duration.seconds) | .contentDetails.duration +thumb | .media$group.media$thumbnail[1].url | .snippet.thumbnails.medium.url +**`NEW`** | - | - +embed | n/a | .status.embeddable + +## Updated URLs +#### base + +base = | `local+'//gdata.youtube.com/'` +------ | ---------------------------- + | **`'https://www.googleapis.com/youtube/v3/'`** + +* *https required for APIv3* + +#### userInfo +``` +Before: +utils.endpoints.base+'feeds/api/users/'+settings.user+'?v=2&alt=json'; +After: +utils.endpoints.base+'channels?'+settings.cid+'&key='+apiKey+'&part=snippet,contentDetails,statistics'; +``` +**Required in Build** +```javascript +if (settings.channelId){ + settings.cid = 'id='+settings.channelId; +} else if(settings.user){ + settings.cid = 'forUsername='+settings.user; +} +``` + +#### userVids +``` +Before: +utils.endpoints.base+'feeds/api/users/'+settings.user+'/uploads/?v=2&alt=json&format=5&max-results=50'; +After: n/a pulled playlistId pulled from userInfo +``` +**Replaced with:** +```javascript +userUploads: function(userInfo){ + if (userInfo && userInfo.items){ + settings.playlist = userInfo.items[0].contentDetails.relatedPlaylists.uploads; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } +} +``` + +#### userPlaylists +``` +Before: +utils.endpoints.base+'feeds/api/users/'+settings.user+'/playlists/?v=2&alt=json&format=5&max-results=50'; +After: +utils.endpoints.base+'playlists?channelId='+settings.channelId+'&key='+apiKey+'&maxResults=50&part=snippet'; +``` + +#### playlistVids +``` +Before: +utils.endpoints.base+'feeds/api/playlists/'+(settings.playlist)+'?v=2&alt=json&format=5&max-results=50'; +After: +utils.endpoints.base+'playlistItems?playlistId='+settings.playlist+'&key='+apiKey+'&maxResults=50&part=contentDetails'; +``` + +#### `NEW` playlistInfo +`utils.endpoints.base+'playlists?id='+settings.playlist+'&key='+apiKey+'&maxResults=50&part=snippet';` +```javascript +selectedPlaylist: function(playlistInfo){ + if (playlistInfo && playlistInfo.items) { + settings.currentPlaylist = playlistInfo.items[0].snippet.title; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } +} +``` + +#### `NEW` videoInfo +`utils.endpoints.base+'videos?id='+settings.videoString+'&key='+apiKey+'&maxResults=50&part=snippet,contentDetails,status,statistics';` +```javascript +compileVideos: function(res){ + if (res && res.items){ + var playlists = res.items, + i; + settings.videoString = ''; + for(i=0; i 9 ? '' + amount : '0' + amount); break; + case 'M': m = (amount > 9 ? '' + amount : '0' + amount); break; + case 'S': s = (amount > 9 ? '' + amount : '0' + amount); break; + default: // ??? profit + } + }); + if (h){ time += h+':';} + if (m){ time += m+':';} else { time += '00:';} + if (s){ time += s;} else { time += '00';} + + return time; +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 3511ae3..a00ca48 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ # YoutubeTV +**NOW with API Version 3 Support!!** A small, slick, library independent YouTube User/Playlist player - - - - - + ## Features * Library Independent @@ -15,16 +12,35 @@ A small, slick, library independent YouTube User/Playlist player * Supports Playlists and Users * jQuery Support * Module Support +* Responsive Support +* Alternative color scheme ## Demos -* [Default Player Options](http://jakiestfu.github.io/Youtube-TV/demos/default.html) -* [Chromeless Player](http://jakiestfu.github.io/Youtube-TV/demos/chromeless.html) -* [Playlist Support](http://jakiestfu.github.io/Youtube-TV/demos/playlists.html) -* [Full Screen Player](http://jakiestfu.github.io/Youtube-TV/demos/fullscreen.html) (Good for a .tv website?) -* [jQuery Support](http://jakiestfu.github.io/Youtube-TV/demos/jquery.html) +* [Default Player Options](http://giorgio003.github.io/Youtube-TV/demos/default.html) +* [Chromeless Player](http://giorgio003.github.io/Youtube-TV/demos/chromeless.html) +* [Playlist Support](http://giorgio003.github.io/Youtube-TV/demos/playlists.html) +* [Full Screen Player](http://giorgio003.github.io/Youtube-TV/demos/fullscreen.html) (Good for a .tv website?) +* [jQuery Support](http://giorgio003.github.io/Youtube-TV/demos/jquery.html) +* [Responsive Support](http://giorgio003.github.io/Youtube-TV/demos/responsive.html) +* [Multiple Players](http://giorgio003.github.io/Youtube-TV/demos/multiplayer.html) + +## Obtaining Youtube API Key + +As part of the update to [Youtube's API version 3](https://developers.google.com/youtube/v3/), it is required to obtain an API Key from [Google's Developer Console](https://console.developers.google.com/). + +To obtain your API Key you can follow this guide using steps 1 to 3. +[https://developers.google.com/youtube/v3/getting-started](https://developers.google.com/youtube/v3/getting-started) + +Then: +4. under APIs & auth > Credentials > Create new Key > (Select) Browser Key +5. (Recommended) Set any referrers to your domain to prevent unauthorized use of your key. +6. (Optional) Additionally disabling any other unused APIs that are enabled. ## Installation +After obtaining your API Key, fill it in `ytv.js` near the top via this line: +`var apiKey = 'YOUR_API_KEY_HERE';` + Include both the `ytv.css` and `ytv.js` in your HTML file and you are good to go. ```html @@ -35,20 +51,58 @@ Include both the `ytv.css` and `ytv.js` in your HTML file and you are good to go ## Usage After your page has loaded, you may call the script like so: - +```html +
+``` ```javascript -var controller = new YTV(element, { - user: 'YoutubeUsername' +document.addEventListener("DOMContentLoaded", function(event) { + var controller = new YTV('YourPlayerID', { + user: 'YoutubeUsername' + }); }); ``` +**Technical Note:** Above `DOMContentLoaded` is preferred for speed on modern browsers (additionally works down to IE9). However the demos are mostly using `window.onload` which is a slower option but compatible with older browsers (IE8 or older). Good examples of this difference can be found [here (microsoft.com)](http://ie.microsoft.com/Testdrive/HTML5/DOMContentLoaded/Default.html) and [here (jspref.com)](http://jsperf.com/onload-vs-domcontentloaded/3) + +**`Note:`** If you are using a newer YouTube channel you may have a **Channel ID** which may look similar to this: `UCXXXXXXXXXXXXXXXXXXXXXX` +If so, use `channelId: 'UCXXXXXXXXXXXXXXXXXXXXXX'` to replace user! + +#### User vs Channel ID +By visiting the intended channel's Youtube home page. The URL should look like one of the following. +##### User ID `user: ''` + + +##### Channel ID `channelId: ''` + + +#### Additional Options + +Additional options you may wish to add to get started. + +In addition to `user` and `channelId` you can also use `playlist` ID's which can also replace `user` and `channelId` or work along side them. +`playlist: 'PLAYLIST_ID_HERE'` +To chain multiple playlists together, separate them by comma's. Example: +`playlist: 'PLAYLIST_ID1,PLAYLIST_ID2,PLAYLIST_ID3'` + +Add this to enable the responsive Youtube-TV player allowing the player to adjust from desktop resolution all the way to mobile devices. +`responsive: true` + +Accent colour will appear beside the active video in the list. This will accept any CSS value from `#FFF` to `orange`. +`accent: '#008D54'` + +If you prefer the `light` theme over the dark, add these 2 options (or mix 'n match if desired). +`playerTheme: 'light'` +`listTheme: 'light'` + +**Don't forget comma's between each option!** `YTV` accepts two parameters. The first is a string of the element ID you want to use as the player, OR it may the element itself. The second parameter is an object of options defined below: -## Settings and Defaults +## Settings, Defaults and Details ```javascript settings = { element: null, user: null, + channelId: null, playlist: '', fullscreen: false, accent: '#fff', @@ -57,6 +111,13 @@ settings = { autoplay: false, chainVideos: true, browsePlaylists: false, + playerTheme: 'dark', + listTheme: 'dark', + responsive: false, + playId:'', + sortList: false, + reverseList: false, + shuffleList: false, wmode: 'opaque', events: { videoReady: function(){}, @@ -67,7 +128,8 @@ settings = { * `element`: The element or element ID to apply the YouTube TV Player to * `user`: (String) The Username of the YouTube user you want to display videos from -* `playlist`: (String) The Playlist ID you would like to load (Overrides `user`) +* `channelId`: (String) The Channel ID of the YouTube channel you want to display videos from (for newer accounts) +* `playlist`: (String) The Playlist ID(s) you would like to load separated by comma's (Overrides `user`) * `browsePlaylists`: (Boolean) If `true` and the specified `user` has YouTube playlists, they will be accessible in the player by clicking the users Username * `fullscreen`: (Boolean) If `true`, the player will take up all the available space on the users browser screen * `accent`: (String) A CSS color string to apply to the accents of the player @@ -75,10 +137,27 @@ settings = { * `annotations`: (Boolean) If `false`, the annotations from the YouTube video will be hidden * `autoplay`: (Boolean) If `true`, the first video in the list will automatically play once the player has loaded * `chainVideos`: (Boolean) If `true`, the next video in que will automatically play after the current video has completed +* `playerTheme`: (String) Sets the youtube player theme. Default is `dark` with an alterative `light` color scheme. +* `listTheme`: (String) Sets the playlist theme. Default is `dark` with a `light` theme to match the alternative player theme. +* `responsive`: (Boolean) If `true`, it enables a responsive design to support various resolutions including mobile devices. Default currently `false`. +* `playId`: (String) On load this option will allow you to select the first video to play/display. Enter the id of the video you want to display. (ie. `.../watch?v=VIDEOID` to `playId: 'VIDEOID'`). +* `sortList`: (Boolean) If `true`, this option will sort the video list by date uploaded. +* `reverseList`: (Boolean) If `true`, this option will reverse order of the videos. (Can be combined with other sorting option to reverse after sorting). +* `shuffleList`: (Boolean) If `true`, this option will shuffle the video list. (Useful for music playlists). * `wmode`: (String) Sets the Window Mode property for transparency, layering, and positioning in the browser. Values can be: `window` - movie plays in its own rectangular window on a web page. `opaque` - the movie hides everything on the page behind it. `transparent` - the background of the HTML page shows through all transparent portions of the movie, this may slow animation performance. * `events`: The respective events will fire when key actions in the player have been called +## Responsive Design +Youtube-TV can now adapt to various changes in resolution based on it's parent canvas/container. Currently set to `max-width:992px` to match Bootstrap's responsive transitions, but can be modified to match any other responsive boilerplate. + +#### Playlist beside +*(image resized on github don't be fooled >_<)* + +#### Playlist on bottom +*Better for mobile devices* + + ## Public Methods ### `destroy` @@ -117,7 +196,7 @@ YTV may be used as a jQuery plugin in the following fashion: $('#frame').ytv({opts}); ``` - + ## Licensing **MIT Licensing** @@ -125,8 +204,3 @@ $('#frame').ytv({opts});

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

- - -## Share Meh! - - diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..a75c854 --- /dev/null +++ b/Todo.md @@ -0,0 +1,24 @@ +## To-do list @Giorgio003 + +- [x] Implement Responsive Design :iphone: +- [x] Add additional colour scheme to match Youtube `light` player +- [x] Create modular settings for responsiveness and themes +- [x] Allow multiple players +- [x] Merge remaining pending pull requests from Original +- [x] Update documentation (ongoing) + +#### Youtube API v3 +- [x] Convert to Youtube API v3 !important +- [ ] v3 pageToken - pages + full list (snowliondev) +- [ ] Clean up code/documentation +- [ ] Optimize API Quota usage (remove any unused variables left from v2) + +#### Additional +- [x] Allow local testing (flag `http:` when testing locally) (later removed with APIv3/https) +- [ ] FAQ/Fix common issues (Empty playlists, no uploads, etc.) +- [ ] Load order, newest, random/shuffle. +- [ ] Additional themes (or modular colour scheme) +- [ ] Different Aspect ratios for responsive (4:3 for larger playist column) +- [ ] Test IE 8-10 (Low Priority unless requested) +- [x] Obtain More Coffee :coffee: +- [ ] Any other suggestions welcome! :smiley: \ No newline at end of file diff --git a/demos/assets/bootstrap.min.css b/demos/assets/bootstrap.min.css new file mode 100644 index 0000000..bd6e04e --- /dev/null +++ b/demos/assets/bootstrap.min.css @@ -0,0 +1,10 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! + * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=184acbd8ae477c221405) + * Config saved to config.json and https://gist.github.com/184acbd8ae477c221405 + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff !important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:hover,a:focus{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important;visibility:hidden !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}} \ No newline at end of file diff --git a/demos/assets/images/channelid.png b/demos/assets/images/channelid.png new file mode 100644 index 0000000..eb8e22a Binary files /dev/null and b/demos/assets/images/channelid.png differ diff --git a/demos/assets/images/default.png b/demos/assets/images/default.png index dcaa461..b571b53 100644 Binary files a/demos/assets/images/default.png and b/demos/assets/images/default.png differ diff --git a/demos/assets/images/playlists.png b/demos/assets/images/playlists.png index d2dbfe8..d07d9d7 100644 Binary files a/demos/assets/images/playlists.png and b/demos/assets/images/playlists.png differ diff --git a/demos/assets/images/responsive1.png b/demos/assets/images/responsive1.png new file mode 100644 index 0000000..b9b976f Binary files /dev/null and b/demos/assets/images/responsive1.png differ diff --git a/demos/assets/images/responsive2.png b/demos/assets/images/responsive2.png new file mode 100644 index 0000000..ac00844 Binary files /dev/null and b/demos/assets/images/responsive2.png differ diff --git a/demos/assets/images/user.png b/demos/assets/images/user.png new file mode 100644 index 0000000..10f105d Binary files /dev/null and b/demos/assets/images/user.png differ diff --git a/demos/assets/ytv.css b/demos/assets/ytv.css new file mode 100644 index 0000000..2f6e3e0 --- /dev/null +++ b/demos/assets/ytv.css @@ -0,0 +1,225 @@ +/* YouTube TV */ + +/* Base Canvas */ +.ytv-canvas{ + display: block; + background: #282828; + overflow: hidden; + font-family: arial, sans-serif; +} +.ytv-canvas ::-webkit-scrollbar{ + border-left: 1px solid #000; + width: 10px; +} +.ytv-canvas ::-webkit-scrollbar-thumb{ + background: rgba(255,255,255,0.1); +} + +/* Video */ +.ytv-video{ + position: absolute; + top: 0; + right: 300px; + bottom: 0; + left: 0; + height: 100%; +} +.ytv-video iframe{ + width: 100%; + height: 100%; + border: none; + outline: none; + display: block; +} + +/* List */ +.ytv-list{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + height: 100%; + width: 300px; +} +.ytv-list-inner{ + overflow: auto; + position: absolute; + top: 52px; + right: 0; + bottom: 0; + left: 0; + -webkit-overflow-scrolling: touch; +} +.ytv-list ul{ + margin: 0; + padding: 0; + list-style-type: none; +} +.ytv-list .ytv-active a{ + border-left: 2px solid #fff; + background: rgba(255,255,255,0.05);; +} +.ytv-list a{ + display: block; + text-decoration: none; + font-size: 11px; + color: #FEFEFE; + padding: 10px; + padding-left: 8px; + border-top: 1px solid rgba(255,255,255,0.1); + border-bottom: 1px solid rgba(0,0,0,0.5); + border-left: 2px solid transparent; +} +.ytv-list a b{ + max-height: 45px; + overflow: hidden; + display: block; + text-overflow: ellipsis; +} +.ytv-list li:first-child a{ border-top: none; } +.ytv-list li:last-child a{ border-bottom: none; } +.ytv-list a:hover, +.ytv-list-header .ytv-playlists a:hover{ background: rgba(255,255,255,0.05); } +.ytv-list a:active, +.ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.05); } + +.ytv-list .ytv-content{ padding-left: 125px; } +.ytv-list .ytv-thumb-stroke{ + position: absolute; + top: 1px; + left: 1px; + bottom: 1px; + right: 1px; + z-index: 2; + outline: 1px solid rgba(255,255,255,0.1); +} +.ytv-list .ytv-thumb{ + float: left; + position: relative; + outline: 1px solid rgba(0,0,0,0.5); +} +.ytv-list .ytv-thumb img{ + width: 120px; + display: block; +} +.ytv-list .ytv-thumb span{ + position: absolute; + bottom: 5px; + right: 5px; + color: #eee; + background: rgba(0,0,0,0.7); + font-size: 11px; + font-weight: bold; + padding: 0px 4px; + + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.ytv-views{ + display: block; + margin-top: 5px; + font-size: 10px; + font-weight: normal; + opacity: 0.3; +} + +.ytv-list-header{ + height: 52px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.ytv-list-header a{ + background: rgba(255,255,255,0.05); + position: relative; + z-index: 10; +} +.ytv-list-header img, +.ytv-list .ytv-playlists .ytv-thumb img{ + width: 30px; + vertical-align: middle; +} +.ytv-list-header span{ + padding-left: 10px; + font-size: 12px; + font-weight: bold; +} + +/* Playlists */ +.ytv-playlists{ + z-index: 9; + position: absolute; + background: #282828; + top:52px; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + display: none; +} +.ytv-playlists img, +.ytv-list-header img{ + float: left; +} +.ytv-playlists a span, +.ytv-list-header a span{ + white-space: nowrap; + padding-left: 10px; + display: block; + overflow: hidden; + text-overflow: ellipsis; +} +.ytv-list-header > a span{ + line-height: 30px; +} +.ytv-list-header .ytv-playlists a{ + background: none; +} +.ytv-playlist-open .ytv-playlists{ + display: block; +} + +/* Modifiers */ +.ytv-relative{ + position: relative; + width: 100%; + height: 100%; +} +.ytv-full{ + position: fixed; + top: 0; + left: 0; + width: 100% !important; + height: 100% !important; + margin: 0 !important; +} +.ytv-arrow { + height: 10px; + width: 0; + position: relative; + top: 10px; + right: 5px; + border: 10px solid transparent; + float: right; + border-top-color: rgba(0,0,0,0.4); + display: none; +} +.ytv-has-playlists .ytv-arrow{ + display: inline-block; +} +.ytv-playlist-open .ytv-arrow{ + border-color: transparent; + border-bottom-color: rgba(0,0,0,0.4); + top: -10px; +} + +.ytv-list-header a:after, +.ytv-clear:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} \ No newline at end of file diff --git a/demos/assets/ytv.js b/demos/assets/ytv.js new file mode 100644 index 0000000..16edf93 --- /dev/null +++ b/demos/assets/ytv.js @@ -0,0 +1,557 @@ +/* + * YouTube TV + * + * Copyright 2013, Jacob Kelley - http://jakiestfu.com/ + * Released under the MIT Licence + * http://opensource.org/licenses/MIT + * + * Github: + * Version: 3.0.4 + */ +/*jslint browser: true, undef:true, unused:true, laxbreak:true, loopfunc:true*/ +/*global define, module, ender */ + +(function(win, doc) { + 'use strict'; + var apiKey = 'AIzaSyCc6EA6M1iaYzC2pzyDitVNvmTU099LPbA'; + var YTV = YTV || function(id, opts){ + + var noop = function(){}, + settings = { + element: null, + user: null, + channelId: null, + fullscreen: false, + accent: '#fff', + controls: true, + annotations: false, + autoplay: false, + chainVideos: true, + browsePlaylists: false, + playerTheme: 'dark', + listTheme: 'dark', + responsive: false, + playId:'', + sortList: false, + reverseList: false, + shuffleList: false, + wmode: 'opaque', + events: { + videoReady: noop, + stateChange: noop + } + }, + + cache = { + data: {}, + remove: function (url) { + delete cache.data[url]; + }, + exist: function (url) { + return cache.data.hasOwnProperty(url) && cache.data[url] !== null; + }, + get: function (url) { + return cache.data[url]; + }, + set: function (url, data) { + cache.remove(url); + cache.data[url] = data; + } + }, + utils = { + events: { + addEvent: function addEvent(element, eventName, func) { + if (element.addEventListener) { + return element.addEventListener(eventName, func, false); + } else if (element.attachEvent) { + return element.attachEvent("on" + eventName, func); + } + }, + removeEvent: function addEvent(element, eventName, func) { + if (element.addEventListener) { + return element.removeEventListener(eventName, func, false); + } else if (element.attachEvent) { + return element.detachEvent("on" + eventName, func); + } + }, + prevent: function(e) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + } + }, + addCSS: function(css){ + var head = doc.getElementsByTagName('head')[0], + style = doc.createElement('style'); + style.type = 'text/css'; + if (style.styleSheet){ + style.styleSheet.cssText = css; + } else { + style.appendChild(doc.createTextNode(css)); + } + head.appendChild(style); + }, + addCommas: function(str){ + var x = str.split('.'), + x1 = x[0], + x2 = x.length > 1 ? '.' + x[1] : '', + rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; + }, + parentUntil: function(el, attr) { + while (el.parentNode) { + if (el.getAttribute && el.getAttribute(attr)){ + return el; + } + el = el.parentNode; + } + return null; + }, + ajax: { + get: function(url, fn){ + if (cache.exist(url)) { + fn.call(this, JSON.parse(cache.get(url))); + } else { + var handle; + if (win.XDomainRequest) { // Proper CORS for IE8,9 + handle = new XDomainRequest(); + handle.onload = function(){ + cache.set(url, handle.responseText); + fn.call(this, JSON.parse(handle.responseText)); + if (Object.prototype.hasOwnProperty.call(JSON.parse(handle.responseText), 'error')){ + cache.remove(url); + var e = JSON.parse(handle.responseText); + console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message); + } + }; + } else if (win.XMLHttpRequest){ // Modern Browsers + handle = new XMLHttpRequest(); + } + handle.onreadystatechange = function(){ + if (handle.readyState === 4 && handle.status === 200){ + cache.set(url, handle.responseText); + fn.call(this, JSON.parse(handle.responseText)); + } else if (handle.readyState === 4){ + var e = JSON.parse(handle.responseText); + console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message); + } + }; + handle.open("GET",url,true); + handle.send(); + } + } + }, + endpoints: { + base: 'https://www.googleapis.com/youtube/v3/', + userInfo: function(){ + return utils.endpoints.base+'channels?'+settings.cid+'&key='+apiKey+'&part=snippet,contentDetails,statistics'; + }, + playlistInfo: function(pid){ + return utils.endpoints.base+'playlists?id='+pid+'&key='+apiKey+'&maxResults=50&part=snippet'; + }, + userPlaylists: function(){ + return utils.endpoints.base+'playlists?channelId='+settings.channelId+'&key='+apiKey+'&maxResults=50&part=snippet'; + }, + playlistVids: function(){ + return utils.endpoints.base+'playlistItems?playlistId='+settings.pid+'&key='+apiKey+'&maxResults=50&part=contentDetails'; + }, + videoInfo: function(){ + return utils.endpoints.base+'videos?id='+settings.videoString+'&key='+apiKey+'&maxResults=50&part=snippet,contentDetails,status,statistics'; + } + }, + deepExtend: function(destination, source) { + var property; + for (property in source) { + if (source[property] && source[property].constructor && source[property].constructor === Object) { + destination[property] = destination[property] || {}; + utils.deepExtend(destination[property], source[property]); + } else { + destination[property] = source[property]; + } + } + return destination; + } + }, + prepare = { + youtube: function(){ + if(typeof YT=='undefined'){ + var tag = doc.createElement('script'); + tag.src = "https://www.youtube.com/iframe_api"; + var firstScriptTag = doc.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + } + }, + build: function(){ + if (settings.channelId){ + settings.cid = 'id='+settings.channelId; + } else if(settings.user){ + settings.cid = 'forUsername='+settings.user; + } + settings.element.className = "ytv-canvas"; + if(settings.fullscreen){ + settings.element.className += " ytv-full"; + } + utils.addCSS( '#'+id+' .ytv-list .ytv-active a{border-left-color: '+(settings.accent)+';}' ); + // Responsive CSS + if(settings.responsive){ + utils.addCSS('#'+id+' .ytv-video{' + +'position: relative; padding-bottom: 39.4%; /* 16:9 of 70%*/' + +'height: 0; width: 70%;' + +'} #'+id+' .ytv-video iframe{' + +'position: absolute; top: 0; left: 0;' + +'} #'+id+' .ytv-list{' + +'width: 30%;' + +'} #'+id+' .ytv-playlist-open .ytv-arrow{' + +'top: 0px;}' + +'@media only screen and (max-width:992px) {' + +'#'+id+' .ytv-list{' + +'position: relative; display: block;' + +'width: 0; padding-bottom: 40%;' + +'left: auto; right: auto;' + +'top: auto; width: 100%;' + +'} #'+id+' .ytv-video{' + +'position: relative; padding-bottom: 56.25%; /* 16:9 */' + +'height: 0; position: relative;' + +'display: block; left: auto;' + +'right: auto; top: auto; width: 100%;' + +'}}' + ); + } + // Temp Scroll Bar fix + if (settings.listTheme == 'dark'){ + utils.addCSS( ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid #000;}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(255,255,255,0.2);}'); + } + // Optional Light List Theme + if(settings.listTheme == 'light'){ + utils.addCSS( ' #'+id+'.ytv-canvas{background: #ccc;}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid rgba(28,28,28,0.1);}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(28,28,28,0.3);}' + + ' #'+id+' .ytv-list .ytv-active a{background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list a{color: #282828; border-top: 1px solid rgba(0,0,0,0.1); border-bottom: 1px solid rgba(204,204,204,0.5);}' + + ' #'+id+' .ytv-list a:hover, #'+id+' .ytv-list-header .ytv-playlists a:hover{ background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list a:active, #'+id+' .ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list .ytv-thumb-stroke{outline: 1px solid rgba(0,0,0,0.1);}' + + ' #'+id+' .ytv-list .ytv-thumb{outline: 1px solid rgba(255,255,255,0.5);}' + + ' #'+id+' .ytv-list-header{-webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2);}' + + ' #'+id+' .ytv-list-header a{background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-playlists{background: #ccc;}' + ); + } + }, + userUploads: function(userInfo){ + if (userInfo && userInfo.items.length > 0){ + settings.pid = userInfo.items[0].contentDetails.relatedPlaylists.uploads; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } else console.log ('Youtube-TV Error: API returned no matches for: '+(settings.channelId ? settings.channelId : settings.user)+'\nPlease ensure it was entered correctly and in the appropriate field shown below. \nuser: \'username\' or channelId: \'UCxxxx...\''); + }, + selectedPlaylist: function(playlistInfo){ + if (playlistInfo && playlistInfo.items.length > 0) { + if (!settings.channelId && !settings.user){ + settings.cid = ('id='+(settings.channelId = playlistInfo.items[0].snippet.channelId)); + } + settings.currentPlaylist = playlistInfo.items[0].snippet.title; + settings.pid = playlistInfo.items[0].id; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } else console.log ('Youtube-TV Error: API returned no matches for playlist(s): '+settings.playlist); + }, + compileVideos: function(res){ + if (res && res.items.length > 0){ + var playlists = res.items, + i; + settings.videoString = ''; + for(i=0; i 0){ + var list = '
    ', + playlists = res.items, + i; + for(i=0; i'; + list += '
    '; + list += ''+(data.title)+''; + list += ''; + } + list += '
'; + + var lh = settings.element.getElementsByClassName('ytv-list-header')[0], + headerLink = lh.children[0]; + headerLink.href="#"; + headerLink.target=""; + headerLink.setAttribute('data-ytv-playlist-toggle', 'true'); + settings.element.getElementsByClassName('ytv-list-header')[0].innerHTML += list; + lh.className += ' ytv-has-playlists'; + } else console.log ('Youtube-TV Error: Returned no playlists'); + }, + compileList: function(data){ + if(data && data.items.length > 0){ + utils.ajax.get( utils.endpoints.userInfo(), function(userInfo){ + var list = '', + user = { + title: userInfo.items[0].snippet.title, + url: '//youtube.com/channel/'+userInfo.items[0].id, + thumb: userInfo.items[0].snippet.thumbnails['default'].url, + summary: userInfo.items[0].snippet.description, + subscribers: userInfo.items[0].statistics.subscriberCount, + views: userInfo.items[0].statistics.viewCount + }, + videos = data.items, + first = true, + i; + settings.channelId = userInfo.items[0].id; + if(settings.currentPlaylist) user.title += ' · '+(settings.currentPlaylist); + if (settings.sortList) videos.sort(function(a,b){if(a.snippet.publishedAt > b.snippet.publishedAt) return -1;if(a.snippet.publishedAt < b.snippet.publishedAt) return 1;return 0;}); + if (settings.reverseList) videos.reverse(); + if (settings.shuffleList) { + videos = function (){for(var j, x, i = videos.length; i; j = Math.floor(Math.random() * i), x = videos[--i], videos[i] = videos[j], videos[j] = x);return videos;}(); + } + + list += ''; + + list += '
'; + settings.element.innerHTML = '
'+list+'
'; + if(settings.element.getElementsByClassName('ytv-active').length===0){ + settings.element.getElementsByTagName('li')[0].className = "ytv-active"; + } + var active = settings.element.getElementsByClassName('ytv-active')[0]; + active.parentNode.parentNode.scrollTop = active.offsetTop; + action.logic.loadVideo(first, settings.autoplay); + + if (settings.playlist){ + utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.playlists ); + } else if(settings.browsePlaylists){ + utils.ajax.get( utils.endpoints.userPlaylists(), prepare.playlists ); + } + + }); + } else console.log ('Youtube-TV Error: Empty video list'); + } + }, + action = { + + logic: { + + playerStateChange: function(d){ + console.log(d); + }, + + loadVideo: function(slug, autoplay){ + var house = settings.element.getElementsByClassName('ytv-video')[0]; + var counter = settings.element.getElementsByClassName('ytv-video-playerContainer').length; + house.innerHTML = '
'; + + cache.player = new YT.Player('ytv-video-player'+id+counter, { + videoId: slug, + events: { + onReady: settings.events.videoReady, + onStateChange: function(e){ + if( (e.target.getPlayerState()===0) && settings.chainVideos ){ + var ns = settings.element.getElementsByClassName('ytv-active')[0].nextSibling, + link = ns.children[0]; + link.click(); + } + settings.events.stateChange.call(this, e); + } + }, + playerVars: { + enablejsapi: 1, + origin: doc.domain, + controls: settings.controls ? 1 : 0, + rel: 0, + showinfo: 0, + iv_load_policy: settings.annotations ? '' : 3, + autoplay: autoplay ? 1 : 0, + theme: settings.playerTheme, + wmode: settings.wmode + } + }); + } + }, + + endpoints: { + videoClick: function(e){ + var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv'); + if(target){ + if(target.getAttribute('data-ytv')){ + // Load Video + utils.events.prevent(e); + var activeEls = settings.element.getElementsByClassName('ytv-active'), + i; + for(i=0; i YouTube TV - + + + +
+
+

This demo shows the multiple players in a responsive template.

+
+
+
+
+
+
+
+

Multi-Responsive Demo

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pretium varius gravida. Aliquam faucibus ex eget nunc semper, sit amet aliquet diam egestas. Nunc ultricies egestas augue. Aenean cursus, magna vel porttitor interdum, nisi nunc ultricies orci, quis rhoncus diam nisi sed massa. Sed tempor eros eget aliquet porttitor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget maximus sapien. Nam sed ligula lectus. Donec a nunc elit.

+
+
+
+
+

← Resize the window to see how the player adjusts. →

+
+
+
+
+
+
+
+
+ + + + + diff --git a/demos/playlists.html b/demos/playlists.html index 004bc50..f0aa8fb 100644 --- a/demos/playlists.html +++ b/demos/playlists.html @@ -2,7 +2,7 @@ YouTube TV - + + + +
+
+

This demo shows the player in a responsive template.

+
+
+
+
+
+
+
+

Responsive Demo

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pretium varius gravida. Aliquam faucibus ex eget nunc semper, sit amet aliquet diam egestas. Nunc ultricies egestas augue. Aenean cursus, magna vel porttitor interdum, nisi nunc ultricies orci, quis rhoncus diam nisi sed massa. Sed tempor eros eget aliquet porttitor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget maximus sapien. Nam sed ligula lectus. Donec a nunc elit.

+
+
+
+
+

← Resize the window to see how the player adjusts. →

+
+
+ + + + + diff --git a/demos/testing.html b/demos/testing.html new file mode 100644 index 0000000..237eab0 --- /dev/null +++ b/demos/testing.html @@ -0,0 +1,55 @@ + + + + YouTube TV + + + + + + +
+
+

This demo shows the player in a test template for new features.

+
+
+
+
+
+
+
+

Testing Demo

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pretium varius gravida. Aliquam faucibus ex eget nunc semper, sit amet aliquet diam egestas. Nunc ultricies egestas augue. Aenean cursus, magna vel porttitor interdum, nisi nunc ultricies orci, quis rhoncus diam nisi sed massa. Sed tempor eros eget aliquet porttitor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget maximus sapien. Nam sed ligula lectus. Donec a nunc elit.

+
+
+
+
+

← Resize the window to see how the player adjusts. →

+
+
+ + + + + diff --git a/src/ytv.css b/src/ytv.css index 751b3d6..2f6e3e0 100644 --- a/src/ytv.css +++ b/src/ytv.css @@ -1,204 +1,191 @@ -/* - * YouTube TV - */ +/* YouTube TV */ -/* - * Base Canvas - */ +/* Base Canvas */ .ytv-canvas{ display: block; background: #282828; overflow: hidden; font-family: arial, sans-serif; - } +} .ytv-canvas ::-webkit-scrollbar{ border-left: 1px solid #000; width: 10px; - } +} .ytv-canvas ::-webkit-scrollbar-thumb{ background: rgba(255,255,255,0.1); - } +} -/* - * Video - */ +/* Video */ .ytv-video{ position: absolute; top: 0; - right: 300px; - bottom: 0; - left: 0; - height: 100%; - } - .ytv-video iframe{ - width: 100%; - height: 100%; - border: none; - outline: none; - display: block; - } + right: 300px; + bottom: 0; + left: 0; + height: 100%; +} +.ytv-video iframe{ + width: 100%; + height: 100%; + border: none; + outline: none; + display: block; +} -/* - * List - */ +/* List */ .ytv-list{ - position: absolute; - top: 0; - right: 0; - bottom: 0; - height: 100%; - width: 300px; - } - .ytv-list-inner{ - overflow: auto; - position: absolute; - top: 52px; - right: 0; - bottom: 0; - left: 0; - -webkit-overflow-scrolling: touch; - } - .ytv-list ul{ - margin: 0; - padding: 0; - list-style-type: none; - } - .ytv-list .ytv-active a{ - border-left: 2px solid #fff; - background: rgba(255,255,255,0.05);; - } - .ytv-list a{ - display: block; - text-decoration: none; - font-size: 11px; - color: #FEFEFE; - padding: 10px; - padding-left: 8px; - border-top: 1px solid rgba(255,255,255,0.1); - border-bottom: 1px solid rgba(0,0,0,0.5); - border-left: 2px solid transparent; - } - .ytv-list a b{ - max-height: 45px; - overflow: hidden; - display: block; - text-overflow: ellipsis; - } - .ytv-list li:first-child a{ border-top: none; } - .ytv-list li:last-child a{ border-bottom: none; } - .ytv-list a:hover, - .ytv-list-header .ytv-playlists a:hover{ background: rgba(255,255,255,0.05); } - .ytv-list a:active, - .ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.05); } - - .ytv-list .ytv-content{ padding-left: 125px; } - .ytv-list .ytv-thumb-stroke{ - position: absolute; - top: 1px; - left: 1px; - bottom: 1px; - right: 1px; - z-index: 2; - outline: 1px solid rgba(255,255,255,0.1); - } - .ytv-list .ytv-thumb{ - float: left; - position: relative; - outline: 1px solid rgba(0,0,0,0.5); - } - .ytv-list .ytv-thumb img{ - width: 120px; - display: block; - } - .ytv-list .ytv-thumb span{ - position: absolute; - bottom: 5px; - right: 5px; - color: #eee; - background: rgba(0,0,0,0.7); - font-size: 11px; - font-weight: bold; - padding: 0px 4px; - - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - } - .ytv-views{ - display: block; - margin-top: 5px; - font-size: 10px; - font-weight: normal; - opacity: 0.3; - } + position: absolute; + top: 0; + right: 0; + bottom: 0; + height: 100%; + width: 300px; +} +.ytv-list-inner{ + overflow: auto; + position: absolute; + top: 52px; + right: 0; + bottom: 0; + left: 0; + -webkit-overflow-scrolling: touch; +} +.ytv-list ul{ + margin: 0; + padding: 0; + list-style-type: none; +} +.ytv-list .ytv-active a{ + border-left: 2px solid #fff; + background: rgba(255,255,255,0.05);; +} +.ytv-list a{ + display: block; + text-decoration: none; + font-size: 11px; + color: #FEFEFE; + padding: 10px; + padding-left: 8px; + border-top: 1px solid rgba(255,255,255,0.1); + border-bottom: 1px solid rgba(0,0,0,0.5); + border-left: 2px solid transparent; +} +.ytv-list a b{ + max-height: 45px; + overflow: hidden; + display: block; + text-overflow: ellipsis; +} +.ytv-list li:first-child a{ border-top: none; } +.ytv-list li:last-child a{ border-bottom: none; } +.ytv-list a:hover, +.ytv-list-header .ytv-playlists a:hover{ background: rgba(255,255,255,0.05); } +.ytv-list a:active, +.ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.05); } + +.ytv-list .ytv-content{ padding-left: 125px; } +.ytv-list .ytv-thumb-stroke{ + position: absolute; + top: 1px; + left: 1px; + bottom: 1px; + right: 1px; + z-index: 2; + outline: 1px solid rgba(255,255,255,0.1); +} +.ytv-list .ytv-thumb{ + float: left; + position: relative; + outline: 1px solid rgba(0,0,0,0.5); +} +.ytv-list .ytv-thumb img{ + width: 120px; + display: block; +} +.ytv-list .ytv-thumb span{ + position: absolute; + bottom: 5px; + right: 5px; + color: #eee; + background: rgba(0,0,0,0.7); + font-size: 11px; + font-weight: bold; + padding: 0px 4px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} +.ytv-views{ + display: block; + margin-top: 5px; + font-size: 10px; + font-weight: normal; + opacity: 0.3; +} - .ytv-list-header{ - height: 52px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - } - .ytv-list-header a{ - background: rgba(255,255,255,0.05); - position: relative; - z-index: 10; - } - .ytv-list-header img, - .ytv-list .ytv-playlists .ytv-thumb img{ - width: 30px; - vertical-align: middle; - } - .ytv-list-header span{ - padding-left: 10px; - font-size: 12px; - font-weight: bold; - } +.ytv-list-header{ + height: 52px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.ytv-list-header a{ + background: rgba(255,255,255,0.05); + position: relative; + z-index: 10; +} +.ytv-list-header img, +.ytv-list .ytv-playlists .ytv-thumb img{ + width: 30px; + vertical-align: middle; +} +.ytv-list-header span{ + padding-left: 10px; + font-size: 12px; + font-weight: bold; +} -/* - * Playlists - */ +/* Playlists */ .ytv-playlists{ - z-index: 9; - position: absolute; - background: #282828; - top:52px; - left: 0; - right: 0; - bottom: 0; - overflow: auto; - display: none; - } - .ytv-playlists img, - .ytv-list-header img{ - float: left; - } - .ytv-playlists a span, - .ytv-list-header a span{ - white-space: nowrap; - padding-left: 10px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - } - .ytv-list-header > a span{ - line-height: 30px; - } - .ytv-list-header .ytv-playlists a{ - background: none; - } - .ytv-playlist-open .ytv-playlists{ - display: block; - } + z-index: 9; + position: absolute; + background: #282828; + top:52px; + left: 0; + right: 0; + bottom: 0; + overflow: auto; + display: none; +} +.ytv-playlists img, +.ytv-list-header img{ + float: left; +} +.ytv-playlists a span, +.ytv-list-header a span{ + white-space: nowrap; + padding-left: 10px; + display: block; + overflow: hidden; + text-overflow: ellipsis; +} +.ytv-list-header > a span{ + line-height: 30px; +} +.ytv-list-header .ytv-playlists a{ + background: none; +} +.ytv-playlist-open .ytv-playlists{ + display: block; +} -/* - * Modifiers - */ +/* Modifiers */ .ytv-relative{ position: relative; width: 100%; height: 100%; - } +} .ytv-full{ position: fixed; top: 0; @@ -206,7 +193,7 @@ width: 100% !important; height: 100% !important; margin: 0 !important; - } +} .ytv-arrow { height: 10px; width: 0; @@ -217,15 +204,15 @@ float: right; border-top-color: rgba(0,0,0,0.4); display: none; - } - .ytv-has-playlists .ytv-arrow{ - display: inline-block; - } - .ytv-playlist-open .ytv-arrow{ - border-color: transparent; - border-bottom-color: rgba(0,0,0,0.4); - top: -10px; - } +} +.ytv-has-playlists .ytv-arrow{ + display: inline-block; +} +.ytv-playlist-open .ytv-arrow{ + border-color: transparent; + border-bottom-color: rgba(0,0,0,0.4); + top: -10px; +} .ytv-list-header a:after, .ytv-clear:after { @@ -235,4 +222,4 @@ visibility: hidden; line-height: 0; height: 0; - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/ytv.js b/src/ytv.js index cebacc9..c84c85d 100644 --- a/src/ytv.js +++ b/src/ytv.js @@ -6,428 +6,557 @@ * http://opensource.org/licenses/MIT * * Github: - * Version: 1.0.2 + * Version: 3.0.5 */ -/*jslint browser: true, undef:true, unused:true*/ +/*jslint browser: true, undef:true, unused:true, laxbreak:true, loopfunc:true*/ /*global define, module, ender */ (function(win, doc) { - 'use strict'; - var YTV = YTV || function(id, opts){ + 'use strict'; + var apiKey = 'YOUR_API_KEY_HERE'; + var YTV = YTV || function(id, opts){ - var noop = function(){}, - settings = { - element: null, - user: null, - fullscreen: false, - accent: '#fff', - controls: true, - annotations: false, - autoplay: false, - chainVideos: true, - browsePlaylists: false, - wmode: 'opaque', - events: { - videoReady: noop, - stateChange: noop - } - }, - - cache = {}, - utils = { - events: { - addEvent: function addEvent(element, eventName, func) { - if (element.addEventListener) { - return element.addEventListener(eventName, func, false); - } else if (element.attachEvent) { - return element.attachEvent("on" + eventName, func); - } - }, - removeEvent: function addEvent(element, eventName, func) { - if (element.addEventListener) { - return element.removeEventListener(eventName, func, false); - } else if (element.attachEvent) { - return element.detachEvent("on" + eventName, func); - } - }, - prevent: function(e) { - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - } - }, - addCSS: function(css){ - var head = doc.getElementsByTagName('head')[0], - style = doc.createElement('style'); - style.type = 'text/css'; - if (style.styleSheet){ - style.styleSheet.cssText = css; - } else { - style.appendChild(doc.createTextNode(css)); - } - head.appendChild(style); - }, - addCommas: function(str){ - var x = str.split('.'), - x1 = x[0], - x2 = x.length > 1 ? '.' + x[1] : '', - rgx = /(\d+)(\d{3})/; - while (rgx.test(x1)) { - x1 = x1.replace(rgx, '$1' + ',' + '$2'); - } - return x1 + x2; - }, - parentUntil: function(el, attr) { - while (el.parentNode) { - if (el.getAttribute && el.getAttribute(attr)){ - return el; - } - el = el.parentNode; - } - return null; - }, - ajax: { - get: function(url, fn){ - var handle; - if (win.XMLHttpRequest){ - handle = new XMLHttpRequest(); - } else { - handle = new ActiveXObject("Microsoft.XMLHTTP"); - } - handle.onreadystatechange = function(){ - if (handle.readyState === 4 && handle.status === 200){ - fn.call(this, JSON.parse(handle.responseText)); - } - }; - handle.open("GET",url,true); - handle.send(); - } - }, - endpoints: { - base: 'http://gdata.youtube.com/', - userInfo: function(){ - return utils.endpoints.base+'feeds/api/users/'+settings.user+'?v=2&alt=json'; - }, - userVids: function(){ - return utils.endpoints.base+'feeds/api/users/'+settings.user+'/uploads/?v=2&alt=json&format=5&max-results=50'; - }, - userPlaylists: function(){ - return utils.endpoints.base+'feeds/api/users/'+settings.user+'/playlists/?v=2&alt=json&format=5&max-results=50'; - }, - playlistVids: function(){ - return utils.endpoints.base+'feeds/api/playlists/'+(settings.playlist)+'?v=2&alt=json&format=5&max-results=50'; - } - }, - deepExtend: function(destination, source) { - var property; - for (property in source) { - if (source[property] && source[property].constructor && source[property].constructor === Object) { - destination[property] = destination[property] || {}; - utils.deepExtend(destination[property], source[property]); - } else { - destination[property] = source[property]; - } - } - return destination; - } - }, - prepare = { - youtube: function(fn){ - var tag = doc.createElement('script'); - tag.src = "https://www.youtube.com/iframe_api"; - var firstScriptTag = doc.getElementsByTagName('script')[0]; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - win.onYouTubeIframeAPIReady = fn; - }, - build: function(){ - settings.element.className = "ytv-canvas"; - if(settings.fullscreen){ - settings.element.className += " ytv-full"; - } - utils.addCSS( '.ytv-list .ytv-active a{border-left-color: '+(settings.accent)+';}' ); - }, - playlists: function(res){ - if(res && res.feed){ - var list = '
    ', - playlists = res.feed.entry, - i; - for(i=0; i'; - list += '
    '; - list += ''+(data.title)+''; - list += ''; - } - list += '
'; - - var lh = doc.getElementsByClassName('ytv-list-header')[0], - headerLink = lh.children[0]; - headerLink.href="#"; - headerLink.target=""; - headerLink.setAttribute('data-ytv-playlist-toggle', 'true'); - doc.getElementsByClassName('ytv-list-header')[0].innerHTML += list; - lh.className += ' ytv-has-playlists'; - } - }, - compileList: function(data){ - if(data && data.feed){ - utils.ajax.get( utils.endpoints.userInfo(), function(userInfo){ - var list = '', - user = { - title: userInfo.entry.title.$t, - url: 'http://youtube.com/user/'+userInfo.entry.yt$username.$t, - thumb: userInfo.entry.media$thumbnail.url, - summary: userInfo.entry.summary.$t, - subscribers: userInfo.entry.yt$statistics.subscriberCount, - views: userInfo.entry.yt$statistics.totalUploadViews - }, - videos = data.feed.entry, - first = true, - i; - if(settings.playlist){ - user.title += ' · '+(data.feed.media$group.media$title.$t); - } - list += ''; - - list += '
    '; - for(i=0; i 1 ? '.' + x[1] : '', + rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; + }, + parentUntil: function(el, attr) { + while (el.parentNode) { + if (el.getAttribute && el.getAttribute(attr)){ + return el; + } + el = el.parentNode; + } + return null; + }, + ajax: { + get: function(url, fn){ + if (cache.exist(url)) { + fn.call(this, JSON.parse(cache.get(url))); + } else { + var handle; + if (win.XDomainRequest) { // Proper CORS for IE8,9 + handle = new XDomainRequest(); + handle.onload = function(){ + cache.set(url, handle.responseText); + fn.call(this, JSON.parse(handle.responseText)); + if (Object.prototype.hasOwnProperty.call(JSON.parse(handle.responseText), 'error')){ + cache.remove(url); + var e = JSON.parse(handle.responseText); + console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message); + } + }; + } else if (win.XMLHttpRequest){ // Modern Browsers + handle = new XMLHttpRequest(); + } + handle.onreadystatechange = function(){ + if (handle.readyState === 4 && handle.status === 200){ + cache.set(url, handle.responseText); + fn.call(this, JSON.parse(handle.responseText)); + } else if (handle.readyState === 4){ + var e = JSON.parse(handle.responseText); + console.log('Youtube-TV Error: Youtube API Response: '+e.error.errors[0].reason+'\n'+ 'Details: '+e.error.errors[0].message); + } + }; + handle.open("GET",url,true); + handle.send(); + } + } + }, + endpoints: { + base: 'https://www.googleapis.com/youtube/v3/', + userInfo: function(){ + return utils.endpoints.base+'channels?'+settings.cid+'&key='+apiKey+'&part=snippet,contentDetails,statistics'; + }, + playlistInfo: function(pid){ + return utils.endpoints.base+'playlists?id='+pid+'&key='+apiKey+'&maxResults=50&part=snippet'; + }, + userPlaylists: function(){ + return utils.endpoints.base+'playlists?channelId='+settings.channelId+'&key='+apiKey+'&maxResults=50&part=snippet'; + }, + playlistVids: function(){ + return utils.endpoints.base+'playlistItems?playlistId='+settings.pid+'&key='+apiKey+'&maxResults=50&part=contentDetails'; + }, + videoInfo: function(){ + return utils.endpoints.base+'videos?id='+settings.videoString+'&key='+apiKey+'&maxResults=50&part=snippet,contentDetails,status,statistics'; + } + }, + deepExtend: function(destination, source) { + var property; + for (property in source) { + if (source[property] && source[property].constructor && source[property].constructor === Object) { + destination[property] = destination[property] || {}; + utils.deepExtend(destination[property], source[property]); + } else { + destination[property] = source[property]; + } + } + return destination; + } + }, + prepare = { + youtube: function(){ + if(typeof YT=='undefined'){ + var tag = doc.createElement('script'); + tag.src = "https://www.youtube.com/iframe_api"; + var firstScriptTag = doc.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + } + }, + build: function(){ + if (settings.channelId){ + settings.cid = 'id='+settings.channelId; + } else if(settings.user){ + settings.cid = 'forUsername='+settings.user; + } + settings.element.className = "ytv-canvas"; + if(settings.fullscreen){ + settings.element.className += " ytv-full"; + } + utils.addCSS( '#'+id+' .ytv-list .ytv-active a{border-left-color: '+(settings.accent)+';}' ); + // Responsive CSS + if(settings.responsive){ + utils.addCSS('#'+id+' .ytv-video{' + +'position: relative; padding-bottom: 39.4%; /* 16:9 of 70%*/' + +'height: 0; width: 70%;' + +'} #'+id+' .ytv-video iframe{' + +'position: absolute; top: 0; left: 0;' + +'} #'+id+' .ytv-list{' + +'width: 30%;' + +'} #'+id+' .ytv-playlist-open .ytv-arrow{' + +'top: 0px;}' + +'@media only screen and (max-width:992px) {' + +'#'+id+' .ytv-list{' + +'position: relative; display: block;' + +'width: 0; padding-bottom: 40%;' + +'left: auto; right: auto;' + +'top: auto; width: 100%;' + +'} #'+id+' .ytv-video{' + +'position: relative; padding-bottom: 56.25%; /* 16:9 */' + +'height: 0; position: relative;' + +'display: block; left: auto;' + +'right: auto; top: auto; width: 100%;' + +'}}' + ); + } + // Temp Scroll Bar fix + if (settings.listTheme == 'dark'){ + utils.addCSS( ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid #000;}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(255,255,255,0.2);}'); + } + // Optional Light List Theme + if(settings.listTheme == 'light'){ + utils.addCSS( ' #'+id+'.ytv-canvas{background: #ccc;}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar{border-left: 1px solid rgba(28,28,28,0.1);}' + + ' #'+id+'.ytv-canvas ::-webkit-scrollbar-thumb{background: rgba(28,28,28,0.3);}' + + ' #'+id+' .ytv-list .ytv-active a{background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list a{color: #282828; border-top: 1px solid rgba(0,0,0,0.1); border-bottom: 1px solid rgba(204,204,204,0.5);}' + + ' #'+id+' .ytv-list a:hover, #'+id+' .ytv-list-header .ytv-playlists a:hover{ background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list a:active, #'+id+' .ytv-list-header .ytv-playlists a:active{ background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-list .ytv-thumb-stroke{outline: 1px solid rgba(0,0,0,0.1);}' + + ' #'+id+' .ytv-list .ytv-thumb{outline: 1px solid rgba(255,255,255,0.5);}' + + ' #'+id+' .ytv-list-header{-webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2); box-shadow: 0 1px 2px rgba(255, 255, 255, 0.2);}' + + ' #'+id+' .ytv-list-header a{background: rgba(0,0,0,0.2);}' + + ' #'+id+' .ytv-playlists{background: #ccc;}' + ); + } + }, + userUploads: function(userInfo){ + if (userInfo && userInfo.items.length > 0){ + settings.pid = userInfo.items[0].contentDetails.relatedPlaylists.uploads; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } else console.log ('Youtube-TV Error: API returned no matches for: '+(settings.channelId ? settings.channelId : settings.user)+'\nPlease ensure it was entered correctly and in the appropriate field shown below. \nuser: \'username\' or channelId: \'UCxxxx...\''); + }, + selectedPlaylist: function(playlistInfo){ + if (playlistInfo && playlistInfo.items.length > 0) { + if (!settings.channelId && !settings.user){ + settings.cid = ('id='+(settings.channelId = playlistInfo.items[0].snippet.channelId)); + } + settings.currentPlaylist = playlistInfo.items[0].snippet.title; + settings.pid = playlistInfo.items[0].id; + utils.ajax.get( utils.endpoints.playlistVids(), prepare.compileVideos ); + } else console.log ('Youtube-TV Error: API returned no matches for playlist(s): '+settings.playlist); + }, + compileVideos: function(res){ + if (res && res.items.length > 0){ + var playlists = res.items, + i; + settings.videoString = ''; + for(i=0; i 0){ + var list = '
      ', + playlists = res.items, + i; + for(i=0; i'; + list += '
      '; + list += ''+(data.title)+''; + list += ''; + } + list += '
    '; + + var lh = settings.element.getElementsByClassName('ytv-list-header')[0], + headerLink = lh.children[0]; + headerLink.href="#"; + headerLink.target=""; + headerLink.setAttribute('data-ytv-playlist-toggle', 'true'); + settings.element.getElementsByClassName('ytv-list-header')[0].innerHTML += list; + lh.className += ' ytv-has-playlists'; + } else console.log ('Youtube-TV Error: Returned no playlists'); + }, + compileList: function(data){ + if(data && data.items.length > 0){ + utils.ajax.get( utils.endpoints.userInfo(), function(userInfo){ + var list = '', + user = { + title: userInfo.items[0].snippet.title, + url: '//youtube.com/channel/'+userInfo.items[0].id, + thumb: userInfo.items[0].snippet.thumbnails['default'].url, + summary: userInfo.items[0].snippet.description, + subscribers: userInfo.items[0].statistics.subscriberCount, + views: userInfo.items[0].statistics.viewCount + }, + videos = data.items, + first = true, + i; + settings.channelId = userInfo.items[0].id; + if(settings.currentPlaylist) user.title += ' · '+(settings.currentPlaylist); + if (settings.sortList) videos.sort(function(a,b){if(a.snippet.publishedAt > b.snippet.publishedAt) return -1;if(a.snippet.publishedAt < b.snippet.publishedAt) return 1;return 0;}); + if (settings.reverseList) videos.reverse(); + if (settings.shuffleList) { + videos = function (){for(var j, x, i = videos.length; i; j = Math.floor(Math.random() * i), x = videos[--i], videos[i] = videos[j], videos[j] = x);return videos;}(); + } - list += ''; - list += '
    '+(time)+'
    '; - list += '
    '+(video.title)+''; - if (video.stats) - { - list+''+utils.addCommas(video.stats.viewCount)+' Views'; - } - list += '
    '; - } - } - list += '
'; - settings.element.innerHTML = '
'+list+'
'; - - action.logic.loadVideo(first, settings.autoplay); - - if(settings.browsePlaylists){ - utils.ajax.get( utils.endpoints.userPlaylists(), prepare.playlists ); - } - - }); - } - } - }, - action = { - - logic: { - - playerStateChange: function(d){ - console.log(d); - }, - - loadVideo: function(slug, autoplay){ - - var house = doc.getElementsByClassName('ytv-video')[0]; - house.innerHTML = '
'; - - cache.player = new YT.Player('ytv-video-player', { - videoId: slug, - events: { - onReady: settings.events.videoReady, - onStateChange: function(e){ - if( (e.target.getPlayerState()===0) && settings.chainVideos ){ - var ns = doc.getElementsByClassName('ytv-active')[0].nextSibling, - link = ns.children[0]; - link.click(); - } - settings.events.stateChange.call(this, e); - } - }, - playerVars: { - enablejsapi: 1, - origin: doc.domain, - controls: settings.controls ? 1 : 0, - rel: 0, - showinfo: 0, - iv_load_policy: settings.annotations ? '' : 3, - autoplay: autoplay ? 1 : 0, - wmode: settings.wmode - } - }); - - } - - }, - - endpoints: { - videoClick: function(e){ - var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv'); - - if(target){ - - if(target.getAttribute('data-ytv')){ - - // Load Video - utils.events.prevent(e); - - var activeEls = doc.getElementsByClassName('ytv-active'), - i; - for(i=0; i'; + list += ''; + list += ''+(user.title)+''; + list += ''; + list += ''; + + list += '
'; + settings.element.innerHTML = '
'+list+'
'; + if(settings.element.getElementsByClassName('ytv-active').length===0){ + settings.element.getElementsByTagName('li')[0].className = "ytv-active"; + } + var active = settings.element.getElementsByClassName('ytv-active')[0]; + active.parentNode.parentNode.scrollTop = active.offsetTop; + action.logic.loadVideo(first, settings.autoplay); + + if (settings.playlist){ + utils.ajax.get( utils.endpoints.playlistInfo(settings.playlist), prepare.playlists ); + } else if(settings.browsePlaylists){ + utils.ajax.get( utils.endpoints.userPlaylists(), prepare.playlists ); + } + + }); + } else console.log ('Youtube-TV Error: Empty video list'); + } + }, + action = { + + logic: { + + playerStateChange: function(d){ + console.log(d); + }, + + loadVideo: function(slug, autoplay){ + var house = settings.element.getElementsByClassName('ytv-video')[0]; + var counter = settings.element.getElementsByClassName('ytv-video-playerContainer').length; + house.innerHTML = '
'; + + cache.player = new YT.Player('ytv-video-player'+id+counter, { + videoId: slug, + events: { + onReady: settings.events.videoReady, + onStateChange: function(e){ + if( (e.target.getPlayerState()===0) && settings.chainVideos ){ + var ns = settings.element.getElementsByClassName('ytv-active')[0].nextSibling, + link = ns.children[0]; + link.click(); + } + settings.events.stateChange.call(this, e); + } + }, + playerVars: { + enablejsapi: 1, + origin: doc.domain, + controls: settings.controls ? 1 : 0, + rel: 0, + showinfo: 0, + iv_load_policy: settings.annotations ? '' : 3, + autoplay: autoplay ? 1 : 0, + theme: settings.playerTheme, + wmode: settings.wmode + } + }); + } + }, + + endpoints: { + videoClick: function(e){ + var target = utils.parentUntil(e.target ? e.target : e.srcElement, 'data-ytv'); + if(target){ + if(target.getAttribute('data-ytv')){ + // Load Video + utils.events.prevent(e); + var activeEls = settings.element.getElementsByClassName('ytv-active'), + i; + for(i=0; i