1 videos=true 2 page.title=Videos 3 @jd:body 4 5 <script src="http://swfobject.googlecode.com/svn/trunk/swfobject/swfobject.js" type="text/javascript"></script> 6 <script src="{@docRoot}assets/jquery-history.js" type="text/javascript"></script> 7 <script type="text/javascript"> 8 // for debugging in FF, so other browsers ignore the console commands. 9 var console; 10 if (!console) console = { 'log': function() {} }; 11 12 /* This 'playlist' object defines the playlist IDs for each tab. 13 * Each name inside 'playlist' corresponds to class names for the tab that the playlists belong to (eg: "googleioTab" and "googleioBox" divs). 14 * Each string in 'ids' is the ID of a YouTube playlist that belongs in the corresponding tab. 15 */ 16 var playlists = { 17 'googleio' : { 18 'ids': ["734A052F802C96B9"] 19 }, 20 'about' : { 21 'ids': ["D7C64411AF40DEA5","611F8C5DBF49CEC6"] 22 }, 23 'developertips' : { 24 'ids': ["43E15866EF0033A2"] 25 }, 26 'developersandbox' : { 27 'ids': ["77426907BBAD558E"] 28 } 29 }; 30 31 /* Some playlists include the title in the description meta-data, so we need to account for this when building the thumbnail lists, so we don't show the title twice 32 * This string is read via indexOf(), so multiple IDs need only be comma-separated in this string. 33 */ 34 var playlistsWithTitleInDescription = "734A052F802C96B9"; 35 36 /* This 'featured' object defines the Feature Videos list. 37 * Each playlist ID is paired with a custom video description. 38 */ 39 var featured = { 40 // Android Development Tools 41 'Oq05KqjXTvs' : "The team behind the Android Development Tools demonstrate several powerful features for app development, including new capabilities in the Eclipse layout editor.", 42 // Android UIs for phones and tablets 43 'WGIU2JX1U5Y' : "This talk from the Android UI team explains several design patterns that the team recommends you use when designing your application for screens of all sizes.", 44 // Android Protips 45 'twmuBbC_oB8' : "In this talk, you'll learn how to create a well polished app that abides by several key virtues, using advanced development techniques and some lesser known APIs." 46 }; 47 48 /* When an event on the browser history occurs (back, forward, load), 49 * load the video found in the URL hash 50 */ 51 $(window).history(function(e, hash) { 52 if (location.href.indexOf("#v=") != -1) { 53 videoId = location.href.split("#v="); 54 clickVideo(videoId[1]); // click the link with a matching class 55 } 56 }); 57 58 /* Load a video into the player box. 59 * @param id The YouTube video ID 60 * @param title The video title to display in the player box (character escaped) 61 * @param autoplay Whether to automatically play the video 62 */ 63 function loadVideo(id, title, autoplay) { 64 if($("." + id).hasClass("noplay")) { 65 //console.log("noplay"); 66 autoplay = false; 67 $("." + id).removeClass("noplay"); 68 } 69 swfobject.embedSWF('http://www.youtube.com/v/' + id + '&rel=1&border=0&fs=1&autoplay=' + 70 (autoplay?1:0), 'player', '500', '334', '9.0.0', false, false, {allowfullscreen: 'true'}); 71 $("#videoPlayerTitle").html("<h2>" + unescape(title) + "</h2>"); 72 73 $.history.add('v=' + id); // add the current video to the browser history 74 document.getElementById("doc-content").scrollTop = 0; // scroll the window to the top 75 } 76 77 /* Draw all videos from a playlist into a 'videoPreviews' list 78 * @param data The feed data returned from the youtube request 79 */ 80 function renderPlaylist(data) { 81 var MAX_DESC_LENGTH = 390; // the length at which we will trim the description 82 var feed = data.feed; 83 var entries = feed.entry || []; 84 var playlistId = feed.yt$playlistId.$t; 85 86 var ul = $('<ul class="videoPreviews" />'); 87 88 // Loop through each entry (each video) and add it to the 'videoPreviews' list 89 for (var i = 0; i < entries.length; i++) { 90 var entry = entries[i]; 91 92 var title = entry.title.$t; 93 var id = entry.media$group.yt$videoid.$t; 94 var thumbUrl = entry.media$group.media$thumbnail[0].url; 95 var fullDescription = entry.media$group.media$description.$t; 96 var playerUrl = entry.media$group.media$content[0].url; 97 98 // Check whether this playlist includes the video title inside the description meta-data, so we can remove it 99 if (playlistsWithTitleInDescription.indexOf(playlistId) != -1) { 100 var lines = fullDescription.split("\n"); 101 // If the first line includes the first 17 chars from the title, let's use the title from the description instead (because it's a more complete title) 102 // This accounts for, literally, "Google I/O 2009 -", which is (so far) the min AND max for properly identifying a title in the only playlist with titles in the description 103 if (lines[0].indexOf(title.slice(0,16)) != -1) { 104 h3Title = "<h3>" + lines[0] + "</h3>"; 105 if (lines[2].length < 30) lines = lines.slice(3); // also, if the second line is very short (the speaker name), slice it out too 106 else lines = lines.slice(1); // otherwise, slice after the first line 107 } 108 fullDescription = lines.join(""); 109 } 110 111 var shortDescription = fullDescription.substr(0, MAX_DESC_LENGTH); 112 shortDescription += shortDescription.length == MAX_DESC_LENGTH ? "..." : ""; // add ellipsis if we've chopped the description 113 114 var img = $('<img src="' + thumbUrl + '" width="120" height="90"/>'); 115 var a = $('<a class="' + id + '" href="#" onclick="loadVideo(\'' + id + '\',\'' + escape(title) + '\',true); return setSelected(this);" />'); 116 var pShortDescription = $('<p class="short">' + shortDescription + '</p>'); 117 var pFullDescription = $('<p class="full">' + fullDescription + '</p>'); 118 var h3Title = "<h3>" + title + "</h3>"; 119 var pToggle = "<p class='toggle'><a href='#' onclick='return toggleDescription(this)'><span class='more'>more</span><span class='less'>less</span></a></p>"; 120 var li = $('<li/>'); 121 122 li.append(a); 123 a.append(img).append(h3Title).append(pShortDescription); 124 125 // Add the full description and "more/less" toggle, if necessary 126 if (fullDescription.length > MAX_DESC_LENGTH) { 127 a.append(pFullDescription); 128 li.append(pToggle); 129 } 130 131 ul.append(li); 132 } 133 134 // Now add the 'videoPreviews' list to the page, and be sure we put it in the right tab 135 // This is the part that allows us to put multiple playlists in one tab 136 for (var x in playlists) { 137 var ids = playlists[x].ids; 138 for (var i in ids) { 139 if (ids[i] == playlistId) { 140 $("#"+x+"Box").append(ul); 141 break; 142 } 143 } 144 } 145 } 146 147 /* Draw a featured video into the existing 'videoPreviews' list 148 * @param data The video data returned from the youtube request 149 */ 150 function renderFeatured(data) { 151 var MAX_TITLE_LENGTH = 48; 152 var entry = data.entry || []; 153 var id = entry.media$group.yt$videoid.$t; 154 var description = featured[id]; 155 var title = entry.title.$t; 156 var thumbUrl = entry.media$group.media$thumbnail[0].url; 157 var playerUrl = entry.media$group.media$content[0].url; 158 159 var ellipsis = title.length > MAX_TITLE_LENGTH ? "..." : ""; 160 161 var h3Title = "<h3>"+ title.substr(0,MAX_TITLE_LENGTH) + ellipsis + "</h3>"; 162 var img = $('<img src="' + thumbUrl + '" width="120" height="90"/>'); 163 var p = $('<p>' + description + '</p>'); 164 var a = $('<a class="' + id + '" href="#" onclick="loadVideo(\'' + id + '\',\'' + title + '\',true); return setSelected(this);" />'); 165 var li = $("<li/>"); 166 167 a.append(h3Title).append(img).append(p); 168 li.append(a); 169 170 $("#mainBodyRight .videoPreviews").append(li); 171 } 172 173 /* Request the playlist feeds from YouTube */ 174 function showPlaylists() { 175 for (var x in playlists) { 176 var ids = playlists[x].ids; 177 for (var i in ids) { 178 var script = "<script type='text/javascript' src='http://gdata.youtube.com/feeds/api/playlists/" 179 + ids[i] + 180 "?v=2&alt=json-in-script&max-results=50&callback=renderPlaylist'><\/script>"; 181 $("body").append(script); 182 } 183 } 184 } 185 186 /* Request the featured videos from YouTube */ 187 function showFeatured() { 188 for (var id in featured) { 189 var script = "<script type='text/javascript' src='http://gdata.youtube.com/feeds/api/videos/" 190 + id + 191 "?v=2&alt=json-in-script&callback=renderFeatured'><\/script>"; 192 $("body").append(script); 193 } 194 } 195 196 /* Reveal a tab (playlist) box 197 * @param name The name of the tab 198 */ 199 function showBox(name) { 200 $("#"+name+"Box").addClass("selected").siblings().removeClass("selected"); 201 $("#"+name+"Tab").addClass("selected").siblings().removeClass("selected"); 202 return false; 203 } 204 205 /* Highlight a video thumbnail, including all duplicates that there may be 206 * @param link The link <a> object that was clicked 207 */ 208 function setSelected(link) { 209 var videoId = $(link).attr("class"); 210 if (videoId.indexOf("selected") != -1) { // this means this video is already selected and playing, so bail out 211 return false; 212 } 213 $(".videoPreviews .selected").removeClass("selected"); 214 $("a." + videoId).addClass("selected").each( function (i) { 215 if ($(this).is(":hidden")) { 216 var boxName = $(this).parent().parent().parent().attr("id").split("Box"); 217 $("#"+boxName[0]+"Tab a").click(); 218 } 219 }); 220 return false; 221 } 222 223 /* Reveal and hide the long/short descriptions for a video in the playlist 224 * @param link The link <a> object that was clicked 225 */ 226 function toggleDescription(link) { 227 var aToggle = $(link); 228 $("span", aToggle).toggle(); 229 var aDescription = $(">a", aToggle.parent().parent()); 230 $("p.short", aDescription).toggle(); 231 $("p.full", aDescription).toggle(); 232 if ($("span.less", aToggle).is(":visible")) { 233 aDescription.css("height", "auto"); 234 } else { 235 aDescription.css("height", "90px"); 236 } 237 return false; 238 } 239 240 /* Add actions to the page onload event so that we load a video right away */ 241 addLoadEvent(function () { 242 // if there's a video url in the hash, click that video 243 if (location.href.indexOf("#v=") != -1) { 244 var videoId = location.href.split("#v="); 245 clickVideo(videoId[1]); 246 } else { // otherwise, click the default video 247 clickDefaultVideo(); 248 } 249 }); 250 251 252 var clickVideoAttempts = 0; // Used with clickVideo() 253 254 /* Click a video in order to load it and select it 255 * @param videoId The ID of the video to click 256 */ 257 function clickVideo(videoId) { 258 if (!isAlphaNumeric(videoId)) { 259 clickDefaultVideo(); 260 return; 261 } 262 263 if ($("." + videoId).length != 0) { // if we find the video, click it and return 264 $("." + videoId).addClass("noplay"); // add class to indicate we should NOT autoplay (class removed by loadVideo) 265 $("." + videoId + ":first").click(); 266 return; 267 } else { // if we don't find it, increment clickVideoAttempts 268 console.log("video NOT found: " + videoId); 269 clickVideoAttempts++; 270 } 271 272 // if we don't find it after 20 attempts (2 seconds), click the first feature video 273 if (clickVideoAttempts > 10) { 274 console.log("video never found, clicking default..."); 275 clickVideoAttempts = 0; 276 clickDefaultVideo(); 277 } else { // try again after 100 milliseconds 278 setTimeout('clickVideo("' + videoId + '")', 100); 279 } 280 } 281 282 /* returns true if the provided text is alphanumeric, false otherwise 283 TODO: move this to the dev site js library */ 284 function isAlphaNumeric(text){ 285 var regex=/^[0-9A-Za-z]+$/; //^[a-zA-z]+$/ 286 if(regex.test(text)){ 287 return true; 288 } else { 289 console.log("Bogus video ID"); 290 return false; 291 } 292 } 293 294 /* Click the default video that should be loaded on page load (the first video in the featured list) */ 295 function clickDefaultVideo() { 296 if ($("#mainBodyRight .videoPreviews a:first").length != 0) { 297 var videoId = $("#mainBodyRight .videoPreviews a:first").attr("class"); 298 $("." + videoId).addClass("noplay"); // add class to indicate we should NOT autoplay (class removed by loadVideo) 299 $("." + videoId + ":first").click(); 300 return; 301 } else { // if we don't find it, increment clickVideoAttempts 302 console.log("default video NOT found"); 303 clickVideoAttempts++; 304 } 305 306 // if we don't find it after 50 attempts (5 seconds), just fail 307 if (clickVideoAttempts > 50) { 308 console.log("default video never found..."); 309 } else { // try again after 100 milliseconds 310 setTimeout('clickDefaultVideo()', 100); 311 } 312 } 313 </script> 314 315 <div id="mainBodyFixed"> 316 317 <div id="mainBodyLeft" class="videoPlayer" > 318 <div id="videoPlayerBox"> 319 <div id="videoBorder"> 320 <div id="videoPlayerTitle"></div> 321 <div id="objectWrapper"> 322 <object id="player"></object> 323 </div> 324 </div> 325 </div> 326 </div><!-- end mainBodyLeft --> 327 328 <div id="mainBodyRight" class="videoPlayer"> 329 <h2>Featured Videos</h2> 330 <ul class="videoPreviews"></ul> 331 </div><!-- end mainBodyRight --> 332 333 <ul id="videoTabs"> 334 <li id="aboutTab" class="selected"><a onclick="return showBox('about');" href="#">About the Platform</a></li> 335 <li id="developertipsTab"><a onclick="return showBox('developertips');" href="#">Developer Tips</a></li> 336 <li id="googleioTab"><a onclick="return showBox('googleio');" href="#">Google I/O Sessions</a></li> 337 <li id="developersandboxTab"><a onclick="return showBox('developersandbox');" href="#">Developer Sandbox</a></li> 338 </ul> 339 340 <div id="videos"> 341 <div id="aboutBox" class="selected"></div> 342 <div id="developertipsBox"></div> 343 <div id="googleioBox"></div> 344 <div id="developersandboxBox"></div> 345 </div> 346 347 </div><!-- end mainBodyFixed --> 348 349 <script type="text/javascript"> 350 // Initialization actions 351 showFeatured(); // load featured videos 352 showPlaylists(); // load playlists 353 </script> 354 355 356