/Application/Core/YouTubeManager.cs

http://yet-another-music-application.googlecode.com/ · C# · 391 lines · 226 code · 51 blank · 114 comment · 20 complexity · 6216d6651ab50769c4d206d01b32473e MD5 · raw file

  1. /**
  2. * YouTubeManager.cs
  3. *
  4. * Takes care of searching and finding music on YouTube
  5. * as well as converting results into TrackData structures.
  6. *
  7. * * * * * * * * *
  8. *
  9. * Copyright 2011 Simplare
  10. *
  11. * This code is part of the Stoffi Music Player Project.
  12. * Visit our website at: stoffiplayer.com
  13. *
  14. * This program is free software; you can redistribute it and/or
  15. * modify it under the terms of the GNU General Public License
  16. * as published by the Free Software Foundation; either version
  17. * 3 of the License, or (at your option) any later version.
  18. *
  19. * See stoffiplayer.com/license for more information.
  20. **/
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Collections.ObjectModel;
  24. using System.Linq;
  25. using System.Runtime.InteropServices;
  26. using System.Text;
  27. using Google.GData.Client;
  28. using Google.GData.Extensions;
  29. using Google.GData.YouTube;
  30. using Google.GData.Extensions.MediaRss;
  31. using Google.YouTube;
  32. namespace Stoffi
  33. {
  34. /// <summary>
  35. /// Represents a manager that takes care of talking to YouTube
  36. /// </summary>
  37. public static class YouTubeManager
  38. {
  39. #region Members
  40. private static YouTubeRequestSettings settings = new YouTubeRequestSettings("Stoffi", "AI39si4y_vkAW2Ngyc2BlMdgkBghua2w5hheyesEI-saNU_CNDIMs5YMPpIBk-HpmFG4qDPAHAvE_YYNWH5qV5S1x5euKKRodw");
  41. #endregion
  42. #region Properties
  43. /// <summary>
  44. /// Gets or sets whether the user has Adobe Flash installed or not
  45. /// </summary>
  46. public static bool HasFlash { get; set; }
  47. /// <summary>
  48. /// Gets the current source of tracks used as ItemsSource for the YouTube track list.
  49. /// </summary>
  50. public static ObservableCollection<TrackData> TrackSource { get; private set; }
  51. #endregion
  52. #region Methods
  53. #region Public
  54. /// <summary>
  55. /// Returns a list of tracks from one of the YouTube feeds
  56. /// </summary>
  57. /// <param name="feed">The feed</param>
  58. /// <returns>An observable collection of TrackData that represents the most viewed YouTube tracks</returns>
  59. public static ObservableCollection<TrackData> TopFeed(string feed)
  60. {
  61. ObservableCollection<TrackData> tracks = new ObservableCollection<TrackData>();
  62. YouTubeRequest request = new YouTubeRequest(settings);
  63. int maxFeedItems = 50;
  64. int i = 1;
  65. Feed<Video> videoFeed = request.Get<Video>(new Uri("http://gdata.youtube.com/feeds/api/standardfeeds/" + feed + "?format=5"));
  66. foreach (Video entry in videoFeed.Entries)
  67. {
  68. if (i++ > maxFeedItems) break;
  69. tracks.Add(CreateTrack(entry));
  70. }
  71. TrackSource = tracks;
  72. return tracks;
  73. }
  74. /// <summary>
  75. /// Searches YouTube for tracks matching a certain query
  76. /// </summary>
  77. /// <param name="query">The query to search for</param>
  78. /// <returns>An observable collection of TrackData with all YouTube tracks that match query</returns>
  79. public static ObservableCollection<TrackData> Search(string query)
  80. {
  81. ObservableCollection<TrackData> tracks = new ObservableCollection<TrackData>();
  82. YouTubeQuery q = new YouTubeQuery(YouTubeQuery.DefaultVideoUri);
  83. q.OrderBy = "relevance";
  84. q.Query = query;
  85. q.Formats.Add(YouTubeQuery.VideoFormat.Embeddable);
  86. q.NumberToRetrieve = 50;
  87. q.SafeSearch = YouTubeQuery.SafeSearchValues.None;
  88. YouTubeRequest request = new YouTubeRequest(settings);
  89. Feed<Video> videoFeed = request.Get<Video>(q);
  90. foreach (Video entry in videoFeed.Entries)
  91. {
  92. tracks.Add(CreateTrack(entry));
  93. }
  94. TrackSource = tracks;
  95. return tracks;
  96. }
  97. /// <summary>
  98. /// Retrieves the URL to the thumbnail for a YouTube track
  99. /// </summary>
  100. /// <param name="track">The YouTube track</param>
  101. public static string GetThumbnail(TrackData track)
  102. {
  103. if (IsYouTube(track))
  104. return "https://img.youtube.com/vi/" + GetYouTubeID(track.Path) + "/1.jpg";
  105. else
  106. return "";
  107. }
  108. /// <summary>
  109. /// Retrieves the URL for a YouTube track
  110. /// </summary>
  111. /// <param name="track">The YouTube track</param>
  112. public static string GetURL(TrackData track)
  113. {
  114. if (IsYouTube(track))
  115. return "https://www.youtube.com/watch?v=" + GetYouTubeID(track.Path);
  116. else
  117. return "";
  118. }
  119. /// <summary>
  120. /// Creates a track using a YouTube video ID
  121. /// </summary>
  122. /// <param name="id">The video ID</param>
  123. /// <returns>A TrackData structure representing the YouTube track</returns>
  124. public static TrackData CreateTrack(string id)
  125. {
  126. YouTubeRequest request = new YouTubeRequest(settings);
  127. Uri url = new Uri("http://gdata.youtube.com/feeds/api/videos/" + id);
  128. Video v = request.Retrieve<Video>(url);
  129. if (v == null)
  130. {
  131. U.L(LogLevel.Warning, "YOUTUBE", "Could not find video with ID '" + id + "'");
  132. return null;
  133. }
  134. return CreateTrack(v);
  135. }
  136. /// <summary>
  137. /// Creates a track using a YouTube video entry
  138. /// </summary>
  139. /// <param name="v">The video entry</param>
  140. /// <returns>A TrackData structure representing the YouTube track</returns>
  141. public static TrackData CreateTrack(Video v)
  142. {
  143. TrackData track = new TrackData();
  144. track.Path = "youtube://" + v.VideoId;
  145. track.Icon = "pack://application:,,,/Platform/Windows/GUI/Images/Icons/YouTube.ico";
  146. track.Bookmarks = new List<double>();
  147. track.Processed = true;
  148. int len = Convert.ToInt32(v.Media.Duration.Seconds);
  149. track.Length = U.TimeSpanToString(new TimeSpan(0, 0, len));
  150. string[] str = splitTitle(v.Title);
  151. track.Artist = str[0];
  152. track.Title = str[1];
  153. track.RawViews = v.ViewCount;
  154. track.RawLength = len;
  155. return track;
  156. }
  157. /// <summary>
  158. /// Checks whether a given track is a youtube track
  159. /// </summary>
  160. /// <param name="t">The track to check</param>
  161. /// <returns>True if the track is a youtube track</returns>
  162. public static bool IsYouTube(TrackData t)
  163. {
  164. return (t != null && IsYouTube(t.Path));
  165. }
  166. /// <summary>
  167. /// Checks whether a given track path corresponds to a youtube track
  168. /// </summary>
  169. /// <param name="path">The path of the track to check</param>
  170. /// <returns>True if the track is a youtube track</returns>
  171. public static bool IsYouTube(string path)
  172. {
  173. return path.StartsWith("youtube://");
  174. }
  175. /// <summary>
  176. /// Extracts the video ID of a YouTube track's path
  177. /// </summary>
  178. /// <param name="path">The path of the track</param>
  179. /// <returns>The video ID</returns>
  180. public static string GetYouTubeID(string path)
  181. {
  182. if (IsYouTube(path))
  183. return path.Substring(10);
  184. throw new Exception("Trying to extract YouTube video ID from non-YouTube track: " + path);
  185. }
  186. #endregion
  187. #region Private
  188. /// <summary>
  189. /// Separates a title by a list of separators
  190. /// and identifies artist and title.
  191. /// </summary>
  192. /// <param name="title">The title to split</param>
  193. /// <returns>An array holding artist and title</returns>
  194. private static string[] splitTitle(string title)
  195. {
  196. foreach (string sep in new[] { "-", ":" })
  197. {
  198. string[] variants = new[]
  199. {
  200. " " + sep + " ",
  201. " " + sep,
  202. sep + " "
  203. };
  204. foreach (string var in variants)
  205. {
  206. if (title.Contains(var))
  207. {
  208. string[] str = title.Split(new[] { var }, 2, StringSplitOptions.None);
  209. string[] prefixes = new[]
  210. {
  211. "by ",
  212. "ft ",
  213. "ft.",
  214. "feat ",
  215. "feat."
  216. };
  217. foreach (string pref in prefixes)
  218. {
  219. if (str[0].ToLower().StartsWith(pref))
  220. return new[] { str[0].Substring(pref.Length), str[1] };
  221. else if (str[1].ToLower().StartsWith(pref))
  222. return new[] { str[1].Substring(pref.Length), str[0] };
  223. }
  224. return new[] { str[0], str[1] };
  225. }
  226. }
  227. }
  228. return new[] { "", title };
  229. }
  230. #endregion
  231. #endregion
  232. }
  233. /// <summary>
  234. /// Describes the interface that the chromeless YouTube player can call via JavaScript
  235. /// </summary>
  236. [ComVisibleAttribute(true)]
  237. public class YouTubePlayerInterface
  238. {
  239. /// <summary>
  240. /// Invoked when an error occurs within the YouTube player
  241. /// </summary>
  242. /// <param name="errorCode">The error code</param>
  243. public void OnVideoError(int errorCode)
  244. {
  245. switch (errorCode)
  246. {
  247. case 2:
  248. U.L(LogLevel.Error, "YOUTUBE", "Player reported that we used bad parameters");
  249. break;
  250. case 100:
  251. U.L(LogLevel.Error, "YOUTUBE", "Player reported that the track has either been removed or marked as private");
  252. break;
  253. case 101:
  254. case 150:
  255. U.L(LogLevel.Error, "YOUTUBE", "Player reported that the track is restricted");
  256. break;
  257. default:
  258. U.L(LogLevel.Error, "YOUTUBE", "Player reported an unknown error code: " + errorCode);
  259. break;
  260. }
  261. DispatchError(errorCode.ToString());
  262. }
  263. /// <summary>
  264. /// Invoked when user tries to play a youtube track but doesn't have flash installed
  265. /// </summary>
  266. public void OnNoFlash()
  267. {
  268. DispatchNoFlashDetected();
  269. }
  270. /// <summary>
  271. /// Invoked when the player changes state
  272. /// </summary>
  273. /// <param name="state">The new state of the player</param>
  274. public void OnStateChanged(int state)
  275. {
  276. switch (state)
  277. {
  278. case -1: // unstarted
  279. break;
  280. case 0: // ended
  281. SettingsManager.MediaState = MediaState.Ended;
  282. break;
  283. case 1: // playing
  284. SettingsManager.MediaState = MediaState.Playing;
  285. break;
  286. case 2: // paused
  287. SettingsManager.MediaState = MediaState.Paused;
  288. break;
  289. case 3: // buffering
  290. break;
  291. case 5: // cued
  292. break;
  293. }
  294. }
  295. /// <summary>
  296. /// Invoked when player is ready
  297. /// </summary>
  298. public void OnPlayerReady()
  299. {
  300. DispatchPlayerReady();
  301. }
  302. /// <summary>
  303. /// Dispatches the ErrorOccured event
  304. /// </summary>
  305. /// <param name="message">The error message</param>
  306. private void DispatchError(string message)
  307. {
  308. if (ErrorOccured != null)
  309. ErrorOccured(this, message);
  310. }
  311. /// <summary>
  312. /// Dispatches the NoFlashDetected event
  313. /// </summary>
  314. private void DispatchNoFlashDetected()
  315. {
  316. if (NoFlashDetected != null)
  317. NoFlashDetected(this, new EventArgs());
  318. }
  319. /// <summary>
  320. /// Dispatches the PlayerReady event
  321. /// </summary>
  322. private void DispatchPlayerReady()
  323. {
  324. if (PlayerReady != null)
  325. PlayerReady(this, new EventArgs());
  326. }
  327. /// <summary>
  328. /// Occurs when there's an error from the player
  329. /// </summary>
  330. public event ErrorEventHandler ErrorOccured;
  331. /// <summary>
  332. /// Occurs when the user tries to play a youtube track but there's no flash installed
  333. /// </summary>
  334. public event EventHandler NoFlashDetected;
  335. /// <summary>
  336. /// Occurs when the player is ready
  337. /// </summary>
  338. public event EventHandler PlayerReady;
  339. }
  340. }