YTPlayerView.m 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. // Copyright 2014 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "YTPlayerView.h"
  15. // These are instances of NSString because we get them from parsing a URL. It would be silly to
  16. // convert these into an integer just to have to convert the URL query string value into an integer
  17. // as well for the sake of doing a value comparison. A full list of response error codes can be
  18. // found here:
  19. // https://developers.google.com/youtube/iframe_api_reference
  20. NSString static *const kYTPlayerStateUnstartedCode = @"-1";
  21. NSString static *const kYTPlayerStateEndedCode = @"0";
  22. NSString static *const kYTPlayerStatePlayingCode = @"1";
  23. NSString static *const kYTPlayerStatePausedCode = @"2";
  24. NSString static *const kYTPlayerStateBufferingCode = @"3";
  25. NSString static *const kYTPlayerStateCuedCode = @"5";
  26. NSString static *const kYTPlayerStateUnknownCode = @"unknown";
  27. // Constants representing playback quality.
  28. NSString static *const kYTPlaybackQualitySmallQuality = @"small";
  29. NSString static *const kYTPlaybackQualityMediumQuality = @"medium";
  30. NSString static *const kYTPlaybackQualityLargeQuality = @"large";
  31. NSString static *const kYTPlaybackQualityHD720Quality = @"hd720";
  32. NSString static *const kYTPlaybackQualityHD1080Quality = @"hd1080";
  33. NSString static *const kYTPlaybackQualityHighResQuality = @"highres";
  34. NSString static *const kYTPlaybackQualityAutoQuality = @"auto";
  35. NSString static *const kYTPlaybackQualityDefaultQuality = @"default";
  36. NSString static *const kYTPlaybackQualityUnknownQuality = @"unknown";
  37. // Constants representing YouTube player errors.
  38. NSString static *const kYTPlayerErrorInvalidParamErrorCode = @"2";
  39. NSString static *const kYTPlayerErrorHTML5ErrorCode = @"5";
  40. NSString static *const kYTPlayerErrorVideoNotFoundErrorCode = @"100";
  41. NSString static *const kYTPlayerErrorNotEmbeddableErrorCode = @"101";
  42. NSString static *const kYTPlayerErrorCannotFindVideoErrorCode = @"105";
  43. NSString static *const kYTPlayerErrorSameAsNotEmbeddableErrorCode = @"150";
  44. // Constants representing player callbacks.
  45. NSString static *const kYTPlayerCallbackOnReady = @"onReady";
  46. NSString static *const kYTPlayerCallbackOnStateChange = @"onStateChange";
  47. NSString static *const kYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange";
  48. NSString static *const kYTPlayerCallbackOnError = @"onError";
  49. NSString static *const kYTPlayerCallbackOnPlayTime = @"onPlayTime";
  50. NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIReady = @"onYouTubeIframeAPIReady";
  51. NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad = @"onYouTubeIframeAPIFailedToLoad";
  52. NSString static *const kYTPlayerEmbedUrlRegexPattern = @"^http(s)://(www.)youtube.com/embed/(.*)$";
  53. NSString static *const kYTPlayerAdUrlRegexPattern = @"^http(s)://pubads.g.doubleclick.net/pagead/conversion/";
  54. NSString static *const kYTPlayerOAuthRegexPattern = @"^http(s)://accounts.google.com/o/oauth2/(.*)$";
  55. NSString static *const kYTPlayerStaticProxyRegexPattern = @"^https://content.googleapis.com/static/proxy.html(.*)$";
  56. NSString static *const kYTPlayerSyndicationRegexPattern = @"^https://tpc.googlesyndication.com/sodar/(.*).html$";
  57. @interface YTPlayerView()
  58. @property (nonatomic, strong) NSURL *originURL;
  59. @property (nonatomic, weak) UIView *initialLoadingView;
  60. @end
  61. @implementation YTPlayerView
  62. - (BOOL)loadWithVideoId:(NSString *)videoId {
  63. return [self loadWithVideoId:videoId playerVars:nil];
  64. }
  65. - (BOOL)loadWithPlaylistId:(NSString *)playlistId {
  66. return [self loadWithPlaylistId:playlistId playerVars:nil];
  67. }
  68. - (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars {
  69. if (!playerVars) {
  70. playerVars = @{};
  71. }
  72. NSDictionary *playerParams = @{ @"videoId" : videoId, @"playerVars" : playerVars };
  73. return [self loadWithPlayerParams:playerParams];
  74. }
  75. - (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars {
  76. // Mutable copy because we may have been passed an immutable config dictionary.
  77. NSMutableDictionary *tempPlayerVars = [[NSMutableDictionary alloc] init];
  78. [tempPlayerVars setValue:@"playlist" forKey:@"listType"];
  79. [tempPlayerVars setValue:playlistId forKey:@"list"];
  80. if (playerVars) {
  81. [tempPlayerVars addEntriesFromDictionary:playerVars];
  82. }
  83. NSDictionary *playerParams = @{ @"playerVars" : tempPlayerVars };
  84. return [self loadWithPlayerParams:playerParams];
  85. }
  86. #pragma mark - Player methods
  87. - (void)playVideo {
  88. [self stringFromEvaluatingJavaScript:@"player.playVideo();"];
  89. }
  90. - (void)pauseVideo {
  91. [self notifyDelegateOfYouTubeCallbackUrl:[NSURL URLWithString:[NSString stringWithFormat:@"ytplayer://onStateChange?data=%@", kYTPlayerStatePausedCode]]];
  92. [self stringFromEvaluatingJavaScript:@"player.pauseVideo();"];
  93. }
  94. - (void)stopVideo {
  95. [self stringFromEvaluatingJavaScript:@"player.stopVideo();"];
  96. }
  97. - (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead {
  98. NSNumber *secondsValue = [NSNumber numberWithFloat:seekToSeconds];
  99. NSString *allowSeekAheadValue = [self stringForJSBoolean:allowSeekAhead];
  100. NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, %@);", secondsValue, allowSeekAheadValue];
  101. [self stringFromEvaluatingJavaScript:command];
  102. }
  103. #pragma mark - Cueing methods
  104. - (void)cueVideoById:(NSString *)videoId
  105. startSeconds:(float)startSeconds
  106. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  107. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  108. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  109. NSString *command = [NSString stringWithFormat:@"player.cueVideoById('%@', %@, '%@');",
  110. videoId, startSecondsValue, qualityValue];
  111. [self stringFromEvaluatingJavaScript:command];
  112. }
  113. - (void)cueVideoById:(NSString *)videoId
  114. startSeconds:(float)startSeconds
  115. endSeconds:(float)endSeconds
  116. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  117. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  118. NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
  119. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  120. NSString *command = [NSString stringWithFormat:@"player.cueVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});", videoId, startSecondsValue, endSecondsValue, qualityValue];
  121. [self stringFromEvaluatingJavaScript:command];
  122. }
  123. - (void)loadVideoById:(NSString *)videoId
  124. startSeconds:(float)startSeconds
  125. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  126. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  127. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  128. NSString *command = [NSString stringWithFormat:@"player.loadVideoById('%@', %@, '%@');",
  129. videoId, startSecondsValue, qualityValue];
  130. [self stringFromEvaluatingJavaScript:command];
  131. }
  132. - (void)loadVideoById:(NSString *)videoId
  133. startSeconds:(float)startSeconds
  134. endSeconds:(float)endSeconds
  135. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  136. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  137. NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
  138. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  139. NSString *command = [NSString stringWithFormat:@"player.loadVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});",videoId, startSecondsValue, endSecondsValue, qualityValue];
  140. [self stringFromEvaluatingJavaScript:command];
  141. }
  142. - (void)cueVideoByURL:(NSString *)videoURL
  143. startSeconds:(float)startSeconds
  144. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  145. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  146. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  147. NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, '%@');",
  148. videoURL, startSecondsValue, qualityValue];
  149. [self stringFromEvaluatingJavaScript:command];
  150. }
  151. - (void)cueVideoByURL:(NSString *)videoURL
  152. startSeconds:(float)startSeconds
  153. endSeconds:(float)endSeconds
  154. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  155. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  156. NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
  157. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  158. NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, %@, '%@');",
  159. videoURL, startSecondsValue, endSecondsValue, qualityValue];
  160. [self stringFromEvaluatingJavaScript:command];
  161. }
  162. - (void)loadVideoByURL:(NSString *)videoURL
  163. startSeconds:(float)startSeconds
  164. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  165. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  166. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  167. NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, '%@');",
  168. videoURL, startSecondsValue, qualityValue];
  169. [self stringFromEvaluatingJavaScript:command];
  170. }
  171. - (void)loadVideoByURL:(NSString *)videoURL
  172. startSeconds:(float)startSeconds
  173. endSeconds:(float)endSeconds
  174. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  175. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  176. NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
  177. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  178. NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, %@, '%@');",
  179. videoURL, startSecondsValue, endSecondsValue, qualityValue];
  180. [self stringFromEvaluatingJavaScript:command];
  181. }
  182. #pragma mark - Cueing methods for lists
  183. - (void)cuePlaylistByPlaylistId:(NSString *)playlistId
  184. index:(int)index
  185. startSeconds:(float)startSeconds
  186. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  187. NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId];
  188. [self cuePlaylist:playlistIdString
  189. index:index
  190. startSeconds:startSeconds
  191. suggestedQuality:suggestedQuality];
  192. }
  193. - (void)cuePlaylistByVideos:(NSArray *)videoIds
  194. index:(int)index
  195. startSeconds:(float)startSeconds
  196. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  197. [self cuePlaylist:[self stringFromVideoIdArray:videoIds]
  198. index:index
  199. startSeconds:startSeconds
  200. suggestedQuality:suggestedQuality];
  201. }
  202. - (void)loadPlaylistByPlaylistId:(NSString *)playlistId
  203. index:(int)index
  204. startSeconds:(float)startSeconds
  205. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  206. NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId];
  207. [self loadPlaylist:playlistIdString
  208. index:index
  209. startSeconds:startSeconds
  210. suggestedQuality:suggestedQuality];
  211. }
  212. - (void)loadPlaylistByVideos:(NSArray *)videoIds
  213. index:(int)index
  214. startSeconds:(float)startSeconds
  215. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  216. [self loadPlaylist:[self stringFromVideoIdArray:videoIds]
  217. index:index
  218. startSeconds:startSeconds
  219. suggestedQuality:suggestedQuality];
  220. }
  221. #pragma mark - Setting the playback rate
  222. - (float)playbackRate {
  223. NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackRate();"];
  224. return [returnValue floatValue];
  225. }
  226. - (void)setPlaybackRate:(float)suggestedRate {
  227. NSString *command = [NSString stringWithFormat:@"player.setPlaybackRate(%f);", suggestedRate];
  228. [self stringFromEvaluatingJavaScript:command];
  229. }
  230. - (NSArray *)availablePlaybackRates {
  231. NSString *returnValue =
  232. [self stringFromEvaluatingJavaScript:@"player.getAvailablePlaybackRates();"];
  233. NSData *playbackRateData = [returnValue dataUsingEncoding:NSUTF8StringEncoding];
  234. NSError *jsonDeserializationError;
  235. NSArray *playbackRates = [NSJSONSerialization JSONObjectWithData:playbackRateData
  236. options:kNilOptions
  237. error:&jsonDeserializationError];
  238. if (jsonDeserializationError) {
  239. return nil;
  240. }
  241. return playbackRates;
  242. }
  243. #pragma mark - Setting playback behavior for playlists
  244. - (void)setLoop:(BOOL)loop {
  245. NSString *loopPlayListValue = [self stringForJSBoolean:loop];
  246. NSString *command = [NSString stringWithFormat:@"player.setLoop(%@);", loopPlayListValue];
  247. [self stringFromEvaluatingJavaScript:command];
  248. }
  249. - (void)setShuffle:(BOOL)shuffle {
  250. NSString *shufflePlayListValue = [self stringForJSBoolean:shuffle];
  251. NSString *command = [NSString stringWithFormat:@"player.setShuffle(%@);", shufflePlayListValue];
  252. [self stringFromEvaluatingJavaScript:command];
  253. }
  254. #pragma mark - Playback status
  255. - (float)videoLoadedFraction {
  256. return [[self stringFromEvaluatingJavaScript:@"player.getVideoLoadedFraction();"] floatValue];
  257. }
  258. - (YTPlayerState)playerState {
  259. NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlayerState();"];
  260. return [YTPlayerView playerStateForString:returnValue];
  261. }
  262. - (float)currentTime {
  263. return [[self stringFromEvaluatingJavaScript:@"player.getCurrentTime();"] floatValue];
  264. }
  265. // Playback quality
  266. - (YTPlaybackQuality)playbackQuality {
  267. NSString *qualityValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackQuality();"];
  268. return [YTPlayerView playbackQualityForString:qualityValue];
  269. }
  270. - (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality {
  271. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  272. NSString *command = [NSString stringWithFormat:@"player.setPlaybackQuality('%@');", qualityValue];
  273. [self stringFromEvaluatingJavaScript:command];
  274. }
  275. #pragma mark - Video information methods
  276. - (NSTimeInterval)duration {
  277. return [[self stringFromEvaluatingJavaScript:@"player.getDuration();"] doubleValue];
  278. }
  279. - (NSURL *)videoUrl {
  280. return [NSURL URLWithString:[self stringFromEvaluatingJavaScript:@"player.getVideoUrl();"]];
  281. }
  282. - (NSString *)videoEmbedCode {
  283. return [self stringFromEvaluatingJavaScript:@"player.getVideoEmbedCode();"];
  284. }
  285. #pragma mark - Playlist methods
  286. - (NSArray *)playlist {
  287. NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylist();"];
  288. NSData *playlistData = [returnValue dataUsingEncoding:NSUTF8StringEncoding];
  289. NSError *jsonDeserializationError;
  290. NSArray *videoIds = [NSJSONSerialization JSONObjectWithData:playlistData
  291. options:kNilOptions
  292. error:&jsonDeserializationError];
  293. if (jsonDeserializationError) {
  294. return nil;
  295. }
  296. return videoIds;
  297. }
  298. - (int)playlistIndex {
  299. NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylistIndex();"];
  300. return [returnValue intValue];
  301. }
  302. #pragma mark - Playing a video in a playlist
  303. - (void)nextVideo {
  304. [self stringFromEvaluatingJavaScript:@"player.nextVideo();"];
  305. }
  306. - (void)previousVideo {
  307. [self stringFromEvaluatingJavaScript:@"player.previousVideo();"];
  308. }
  309. - (void)playVideoAt:(int)index {
  310. NSString *command =
  311. [NSString stringWithFormat:@"player.playVideoAt(%@);", [NSNumber numberWithInt:index]];
  312. [self stringFromEvaluatingJavaScript:command];
  313. }
  314. #pragma mark - Helper methods
  315. - (NSArray *)availableQualityLevels {
  316. NSString *returnValue =
  317. [self stringFromEvaluatingJavaScript:@"player.getAvailableQualityLevels().toString();"];
  318. if(!returnValue) return nil;
  319. NSArray *rawQualityValues = [returnValue componentsSeparatedByString:@","];
  320. NSMutableArray *levels = [[NSMutableArray alloc] init];
  321. for (NSString *rawQualityValue in rawQualityValues) {
  322. YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:rawQualityValue];
  323. [levels addObject:[NSNumber numberWithInt:quality]];
  324. }
  325. return levels;
  326. }
  327. - (BOOL)webView:(UIWebView *)webView
  328. shouldStartLoadWithRequest:(NSURLRequest *)request
  329. navigationType:(UIWebViewNavigationType)navigationType {
  330. if ([request.URL.host isEqual: self.originURL.host]) {
  331. return YES;
  332. } else if ([request.URL.scheme isEqual:@"ytplayer"]) {
  333. [self notifyDelegateOfYouTubeCallbackUrl:request.URL];
  334. return NO;
  335. } else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) {
  336. return [self handleHttpNavigationToUrl:request.URL];
  337. }
  338. return YES;
  339. }
  340. - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
  341. if (self.initialLoadingView) {
  342. [self.initialLoadingView removeFromSuperview];
  343. }
  344. }
  345. /**
  346. * Convert a quality value from NSString to the typed enum value.
  347. *
  348. * @param qualityString A string representing playback quality. Ex: "small", "medium", "hd1080".
  349. * @return An enum value representing the playback quality.
  350. */
  351. + (YTPlaybackQuality)playbackQualityForString:(NSString *)qualityString {
  352. YTPlaybackQuality quality = kYTPlaybackQualityUnknown;
  353. if ([qualityString isEqualToString:kYTPlaybackQualitySmallQuality]) {
  354. quality = kYTPlaybackQualitySmall;
  355. } else if ([qualityString isEqualToString:kYTPlaybackQualityMediumQuality]) {
  356. quality = kYTPlaybackQualityMedium;
  357. } else if ([qualityString isEqualToString:kYTPlaybackQualityLargeQuality]) {
  358. quality = kYTPlaybackQualityLarge;
  359. } else if ([qualityString isEqualToString:kYTPlaybackQualityHD720Quality]) {
  360. quality = kYTPlaybackQualityHD720;
  361. } else if ([qualityString isEqualToString:kYTPlaybackQualityHD1080Quality]) {
  362. quality = kYTPlaybackQualityHD1080;
  363. } else if ([qualityString isEqualToString:kYTPlaybackQualityHighResQuality]) {
  364. quality = kYTPlaybackQualityHighRes;
  365. } else if ([qualityString isEqualToString:kYTPlaybackQualityAutoQuality]) {
  366. quality = kYTPlaybackQualityAuto;
  367. }
  368. return quality;
  369. }
  370. /**
  371. * Convert a |YTPlaybackQuality| value from the typed value to NSString.
  372. *
  373. * @param quality A |YTPlaybackQuality| parameter.
  374. * @return An |NSString| value to be used in the JavaScript bridge.
  375. */
  376. + (NSString *)stringForPlaybackQuality:(YTPlaybackQuality)quality {
  377. switch (quality) {
  378. case kYTPlaybackQualitySmall:
  379. return kYTPlaybackQualitySmallQuality;
  380. case kYTPlaybackQualityMedium:
  381. return kYTPlaybackQualityMediumQuality;
  382. case kYTPlaybackQualityLarge:
  383. return kYTPlaybackQualityLargeQuality;
  384. case kYTPlaybackQualityHD720:
  385. return kYTPlaybackQualityHD720Quality;
  386. case kYTPlaybackQualityHD1080:
  387. return kYTPlaybackQualityHD1080Quality;
  388. case kYTPlaybackQualityHighRes:
  389. return kYTPlaybackQualityHighResQuality;
  390. case kYTPlaybackQualityAuto:
  391. return kYTPlaybackQualityAutoQuality;
  392. default:
  393. return kYTPlaybackQualityUnknownQuality;
  394. }
  395. }
  396. /**
  397. * Convert a state value from NSString to the typed enum value.
  398. *
  399. * @param stateString A string representing player state. Ex: "-1", "0", "1".
  400. * @return An enum value representing the player state.
  401. */
  402. + (YTPlayerState)playerStateForString:(NSString *)stateString {
  403. YTPlayerState state = kYTPlayerStateUnknown;
  404. if ([stateString isEqualToString:kYTPlayerStateUnstartedCode]) {
  405. state = kYTPlayerStateUnstarted;
  406. } else if ([stateString isEqualToString:kYTPlayerStateEndedCode]) {
  407. state = kYTPlayerStateEnded;
  408. } else if ([stateString isEqualToString:kYTPlayerStatePlayingCode]) {
  409. state = kYTPlayerStatePlaying;
  410. } else if ([stateString isEqualToString:kYTPlayerStatePausedCode]) {
  411. state = kYTPlayerStatePaused;
  412. } else if ([stateString isEqualToString:kYTPlayerStateBufferingCode]) {
  413. state = kYTPlayerStateBuffering;
  414. } else if ([stateString isEqualToString:kYTPlayerStateCuedCode]) {
  415. state = kYTPlayerStateQueued;
  416. }
  417. return state;
  418. }
  419. /**
  420. * Convert a state value from the typed value to NSString.
  421. *
  422. * @param quality A |YTPlayerState| parameter.
  423. * @return A string value to be used in the JavaScript bridge.
  424. */
  425. + (NSString *)stringForPlayerState:(YTPlayerState)state {
  426. switch (state) {
  427. case kYTPlayerStateUnstarted:
  428. return kYTPlayerStateUnstartedCode;
  429. case kYTPlayerStateEnded:
  430. return kYTPlayerStateEndedCode;
  431. case kYTPlayerStatePlaying:
  432. return kYTPlayerStatePlayingCode;
  433. case kYTPlayerStatePaused:
  434. return kYTPlayerStatePausedCode;
  435. case kYTPlayerStateBuffering:
  436. return kYTPlayerStateBufferingCode;
  437. case kYTPlayerStateQueued:
  438. return kYTPlayerStateCuedCode;
  439. default:
  440. return kYTPlayerStateUnknownCode;
  441. }
  442. }
  443. #pragma mark - Private methods
  444. /**
  445. * Private method to handle "navigation" to a callback URL of the format
  446. * ytplayer://action?data=someData
  447. * This is how the UIWebView communicates with the containing Objective-C code.
  448. * Side effects of this method are that it calls methods on this class's delegate.
  449. *
  450. * @param url A URL of the format ytplayer://action?data=value.
  451. */
  452. - (void)notifyDelegateOfYouTubeCallbackUrl: (NSURL *) url {
  453. NSString *action = url.host;
  454. // NSLog(@"action:%@",action);
  455. // We know the query can only be of the format ytplayer://action?data=SOMEVALUE,
  456. // so we parse out the value.
  457. NSString *query = url.query;
  458. NSString *data;
  459. if (query) {
  460. data = [query componentsSeparatedByString:@"="][1];
  461. }
  462. if ([action isEqual:kYTPlayerCallbackOnReady]) {
  463. if (self.initialLoadingView) {
  464. [self.initialLoadingView removeFromSuperview];
  465. }
  466. if ([self.delegate respondsToSelector:@selector(playerViewDidBecomeReady:)]) {
  467. [self.delegate playerViewDidBecomeReady:self];
  468. }
  469. } else if ([action isEqual:kYTPlayerCallbackOnStateChange]) {
  470. if ([self.delegate respondsToSelector:@selector(playerView:didChangeToState:)]) {
  471. YTPlayerState state = kYTPlayerStateUnknown;
  472. if ([data isEqual:kYTPlayerStateEndedCode]) {
  473. state = kYTPlayerStateEnded;
  474. } else if ([data isEqual:kYTPlayerStatePlayingCode]) {
  475. state = kYTPlayerStatePlaying;
  476. } else if ([data isEqual:kYTPlayerStatePausedCode]) {
  477. state = kYTPlayerStatePaused;
  478. } else if ([data isEqual:kYTPlayerStateBufferingCode]) {
  479. state = kYTPlayerStateBuffering;
  480. } else if ([data isEqual:kYTPlayerStateCuedCode]) {
  481. state = kYTPlayerStateQueued;
  482. } else if ([data isEqual:kYTPlayerStateUnstartedCode]) {
  483. state = kYTPlayerStateUnstarted;
  484. }
  485. [self.delegate playerView:self didChangeToState:state];
  486. }
  487. } else if ([action isEqual:kYTPlayerCallbackOnPlaybackQualityChange]) {
  488. if ([self.delegate respondsToSelector:@selector(playerView:didChangeToQuality:)]) {
  489. YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:data];
  490. [self.delegate playerView:self didChangeToQuality:quality];
  491. }
  492. } else if ([action isEqual:kYTPlayerCallbackOnError]) {
  493. if ([self.delegate respondsToSelector:@selector(playerView:receivedError:)]) {
  494. YTPlayerError error = kYTPlayerErrorUnknown;
  495. if ([data isEqual:kYTPlayerErrorInvalidParamErrorCode]) {
  496. error = kYTPlayerErrorInvalidParam;
  497. } else if ([data isEqual:kYTPlayerErrorHTML5ErrorCode]) {
  498. error = kYTPlayerErrorHTML5Error;
  499. } else if ([data isEqual:kYTPlayerErrorNotEmbeddableErrorCode] ||
  500. [data isEqual:kYTPlayerErrorSameAsNotEmbeddableErrorCode]) {
  501. error = kYTPlayerErrorNotEmbeddable;
  502. } else if ([data isEqual:kYTPlayerErrorVideoNotFoundErrorCode] ||
  503. [data isEqual:kYTPlayerErrorCannotFindVideoErrorCode]) {
  504. error = kYTPlayerErrorVideoNotFound;
  505. }
  506. [self.delegate playerView:self receivedError:error];
  507. }
  508. } else if ([action isEqualToString:kYTPlayerCallbackOnPlayTime]) {
  509. if ([self.delegate respondsToSelector:@selector(playerView:didPlayTime:)]) {
  510. float time = [data floatValue];
  511. [self.delegate playerView:self didPlayTime:time];
  512. }
  513. } else if ([action isEqualToString:kYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad]) {
  514. if (self.initialLoadingView) {
  515. [self.initialLoadingView removeFromSuperview];
  516. }
  517. }
  518. else if ([action isEqualToString:@"restriction"])
  519. {
  520. self.webView.hidden = true;
  521. }
  522. }
  523. -(NSString*) Embed2VID:(NSString*) iframe_embed
  524. {
  525. NSString* content = iframe_embed;
  526. NSString* pattern = @"(\\w+):\\/\\/([^/:]+)(:\\d*)?([^# ]*\\b)";
  527. // NSLog(@"content: %@",content);
  528. // NSLog(@"pattern: %@",pattern);
  529. if(content==nil||pattern==nil)
  530. return nil;
  531. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
  532. NSTextCheckingResult *match = [regex firstMatchInString:content options:nil range:NSMakeRange(0, content.length)];
  533. int i=0;
  534. // NSLog(@"%d->range with richtext label: %d,%d",i, [match rangeAtIndex:i].location,[match rangeAtIndex:i].length);
  535. // NSLog(@"%d-> range of text: %d,%d ",i+1, [match rangeAtIndex:i+1].location,[match rangeAtIndex:i+1].length);
  536. // NSLog(@"text: %@ ",[content substringWithRange:[match rangeAtIndex:i]]);
  537. return [[content substringWithRange:[match rangeAtIndex:i]] lastPathComponent];
  538. }
  539. - (BOOL)handleHttpNavigationToUrl:(NSURL *) url {
  540. // Usually this means the user has clicked on the YouTube logo or an error message in the
  541. // player. Most URLs should open in the browser. The only http(s) URL that should open in this
  542. // UIWebView is the URL for the embed, which is of the format:
  543. // http(s)://www.youtube.com/embed/[VIDEO ID]?[PARAMETERS]
  544. NSError *error = NULL;
  545. NSRegularExpression *ytRegex =
  546. [NSRegularExpression regularExpressionWithPattern:kYTPlayerEmbedUrlRegexPattern
  547. options:NSRegularExpressionCaseInsensitive
  548. error:&error];
  549. NSTextCheckingResult *ytMatch =
  550. [ytRegex firstMatchInString:url.absoluteString
  551. options:0
  552. range:NSMakeRange(0, [url.absoluteString length])];
  553. NSRegularExpression *adRegex =
  554. [NSRegularExpression regularExpressionWithPattern:kYTPlayerAdUrlRegexPattern
  555. options:NSRegularExpressionCaseInsensitive
  556. error:&error];
  557. NSTextCheckingResult *adMatch =
  558. [adRegex firstMatchInString:url.absoluteString
  559. options:0
  560. range:NSMakeRange(0, [url.absoluteString length])];
  561. NSRegularExpression *syndicationRegex =
  562. [NSRegularExpression regularExpressionWithPattern:kYTPlayerSyndicationRegexPattern
  563. options:NSRegularExpressionCaseInsensitive
  564. error:&error];
  565. NSTextCheckingResult *syndicationMatch =
  566. [syndicationRegex firstMatchInString:url.absoluteString
  567. options:0
  568. range:NSMakeRange(0, [url.absoluteString length])];
  569. NSRegularExpression *oauthRegex =
  570. [NSRegularExpression regularExpressionWithPattern:kYTPlayerOAuthRegexPattern
  571. options:NSRegularExpressionCaseInsensitive
  572. error:&error];
  573. NSTextCheckingResult *oauthMatch =
  574. [oauthRegex firstMatchInString:url.absoluteString
  575. options:0
  576. range:NSMakeRange(0, [url.absoluteString length])];
  577. NSRegularExpression *staticProxyRegex =
  578. [NSRegularExpression regularExpressionWithPattern:kYTPlayerStaticProxyRegexPattern
  579. options:NSRegularExpressionCaseInsensitive
  580. error:&error];
  581. NSTextCheckingResult *staticProxyMatch =
  582. [staticProxyRegex firstMatchInString:url.absoluteString
  583. options:0
  584. range:NSMakeRange(0, [url.absoluteString length])];
  585. if (ytMatch || adMatch || oauthMatch || staticProxyMatch || syndicationMatch) {
  586. return YES;
  587. } else {
  588. [[UIApplication sharedApplication] openURL:url];
  589. return NO;
  590. }
  591. }
  592. /**
  593. * Private helper method to load an iframe player with the given player parameters.
  594. *
  595. * @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters
  596. * to instantiate the HTML5 player with. This differs depending on
  597. * whether a single video or playlist is being loaded.
  598. * @return YES if successful, NO if not.
  599. */
  600. - (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams {
  601. NSDictionary *playerCallbacks = @{
  602. @"onReady" : @"onReady",
  603. @"onStateChange" : @"onStateChange",
  604. @"onPlaybackQualityChange" : @"onPlaybackQualityChange",
  605. @"onError" : @"onPlayerError"
  606. };
  607. NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init];
  608. if (additionalPlayerParams) {
  609. [playerParams addEntriesFromDictionary:additionalPlayerParams];
  610. }
  611. if (![playerParams objectForKey:@"height"]) {
  612. [playerParams setValue:@"100%" forKey:@"height"];
  613. }
  614. if (![playerParams objectForKey:@"width"]) {
  615. [playerParams setValue:@"100%" forKey:@"width"];
  616. }
  617. [playerParams setValue:playerCallbacks forKey:@"events"];
  618. if ([playerParams objectForKey:@"playerVars"]) {
  619. NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init];
  620. [playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]];
  621. if (![playerVars objectForKey:@"origin"]) {
  622. self.originURL = [NSURL URLWithString:@"about:blank"];
  623. } else {
  624. self.originURL = [NSURL URLWithString: [playerVars objectForKey:@"origin"]];
  625. }
  626. } else {
  627. // This must not be empty so we can render a '{}' in the output JSON
  628. [playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"];
  629. }
  630. // Remove the existing webView to reset any state
  631. [self.webView removeFromSuperview];
  632. _webView = [self createNewWebView];
  633. [self addSubview:self.webView];
  634. NSError *error = nil;
  635. NSString *path = [[NSBundle bundleForClass:[YTPlayerView class]] pathForResource:@"YTPlayerView-iframe-player"
  636. ofType:@"html"
  637. inDirectory:@"Assets"];
  638. // in case of using Swift and embedded frameworks, resources included not in main bundle,
  639. // but in framework bundle
  640. if (!path) {
  641. path = [[[self class] frameworkBundle] pathForResource:@"YTPlayerView-iframe-player"
  642. ofType:@"html"
  643. inDirectory:@"Assets"];
  644. }
  645. NSString *embedHTMLTemplate =
  646. [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
  647. if (error) {
  648. NSLog(@"Received error rendering template: %@", error);
  649. return NO;
  650. }
  651. // Render the playerVars as a JSON dictionary.
  652. NSError *jsonRenderingError = nil;
  653. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams
  654. options:NSJSONWritingPrettyPrinted
  655. error:&jsonRenderingError];
  656. if (jsonRenderingError) {
  657. NSLog(@"Attempted configuration of player with invalid playerVars: %@ \tError: %@",
  658. playerParams,
  659. jsonRenderingError);
  660. return NO;
  661. }
  662. NSString *playerVarsJsonString =
  663. [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
  664. NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString];
  665. [self.webView loadHTMLString:embedHTML baseURL: self.originURL];
  666. [self.webView setDelegate:self];
  667. self.webView.allowsInlineMediaPlayback = YES;
  668. self.webView.mediaPlaybackRequiresUserAction = NO;
  669. if ([self.delegate respondsToSelector:@selector(playerViewPreferredInitialLoadingView:)]) {
  670. UIView *initialLoadingView = [self.delegate playerViewPreferredInitialLoadingView:self];
  671. if (initialLoadingView) {
  672. initialLoadingView.frame = self.bounds;
  673. initialLoadingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  674. [self addSubview:initialLoadingView];
  675. self.initialLoadingView = initialLoadingView;
  676. }
  677. }
  678. return YES;
  679. }
  680. /**
  681. * Private method for cueing both cases of playlist ID and array of video IDs. Cueing
  682. * a playlist does not start playback.
  683. *
  684. * @param cueingString A JavaScript string representing an array, playlist ID or list of
  685. * video IDs to play with the playlist player.
  686. * @param index 0-index position of video to start playback on.
  687. * @param startSeconds Seconds after start of video to begin playback.
  688. * @param suggestedQuality Suggested YTPlaybackQuality to play the videos.
  689. * @return The result of cueing the playlist.
  690. */
  691. - (void)cuePlaylist:(NSString *)cueingString
  692. index:(int)index
  693. startSeconds:(float)startSeconds
  694. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  695. NSNumber *indexValue = [NSNumber numberWithInt:index];
  696. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  697. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  698. NSString *command = [NSString stringWithFormat:@"player.cuePlaylist(%@, %@, %@, '%@');",
  699. cueingString, indexValue, startSecondsValue, qualityValue];
  700. [self stringFromEvaluatingJavaScript:command];
  701. }
  702. /**
  703. * Private method for loading both cases of playlist ID and array of video IDs. Loading
  704. * a playlist automatically starts playback.
  705. *
  706. * @param cueingString A JavaScript string representing an array, playlist ID or list of
  707. * video IDs to play with the playlist player.
  708. * @param index 0-index position of video to start playback on.
  709. * @param startSeconds Seconds after start of video to begin playback.
  710. * @param suggestedQuality Suggested YTPlaybackQuality to play the videos.
  711. * @return The result of cueing the playlist.
  712. */
  713. - (void)loadPlaylist:(NSString *)cueingString
  714. index:(int)index
  715. startSeconds:(float)startSeconds
  716. suggestedQuality:(YTPlaybackQuality)suggestedQuality {
  717. NSNumber *indexValue = [NSNumber numberWithInt:index];
  718. NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
  719. NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
  720. NSString *command = [NSString stringWithFormat:@"player.loadPlaylist(%@, %@, %@, '%@');",
  721. cueingString, indexValue, startSecondsValue, qualityValue];
  722. [self stringFromEvaluatingJavaScript:command];
  723. }
  724. /**
  725. * Private helper method for converting an NSArray of video IDs into its JavaScript equivalent.
  726. *
  727. * @param videoIds An array of video ID strings to convert into JavaScript format.
  728. * @return A JavaScript array in String format containing video IDs.
  729. */
  730. - (NSString *)stringFromVideoIdArray:(NSArray *)videoIds {
  731. NSMutableArray *formattedVideoIds = [[NSMutableArray alloc] init];
  732. for (id unformattedId in videoIds) {
  733. [formattedVideoIds addObject:[NSString stringWithFormat:@"'%@'", unformattedId]];
  734. }
  735. return [NSString stringWithFormat:@"[%@]", [formattedVideoIds componentsJoinedByString:@", "]];
  736. }
  737. /**
  738. * Private method for evaluating JavaScript in the WebView.
  739. *
  740. * @param jsToExecute The JavaScript code in string format that we want to execute.
  741. * @return JavaScript response from evaluating code.
  742. */
  743. - (NSString *)stringFromEvaluatingJavaScript:(NSString *)jsToExecute {
  744. return [self.webView stringByEvaluatingJavaScriptFromString:jsToExecute];
  745. }
  746. /**
  747. * Private method to convert a Objective-C BOOL value to JS boolean value.
  748. *
  749. * @param boolValue Objective-C BOOL value.
  750. * @return JavaScript Boolean value, i.e. "true" or "false".
  751. */
  752. - (NSString *)stringForJSBoolean:(BOOL)boolValue {
  753. return boolValue ? @"true" : @"false";
  754. }
  755. #pragma mark - Exposed for Testing
  756. - (void)setWebView:(UIWebView *)webView {
  757. _webView = webView;
  758. }
  759. - (UIWebView *)createNewWebView {
  760. UIWebView *webView = [[UIWebView alloc] initWithFrame:self.bounds];
  761. webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
  762. webView.scrollView.scrollEnabled = NO;
  763. webView.scrollView.bounces = NO;
  764. if ([self.delegate respondsToSelector:@selector(playerViewPreferredWebViewBackgroundColor:)]) {
  765. webView.backgroundColor = [self.delegate playerViewPreferredWebViewBackgroundColor:self];
  766. if (webView.backgroundColor == [UIColor clearColor]) {
  767. webView.opaque = NO;
  768. }
  769. }
  770. return webView;
  771. }
  772. - (void)removeWebView {
  773. [self.webView removeFromSuperview];
  774. self.webView = nil;
  775. }
  776. + (NSBundle *)frameworkBundle {
  777. static NSBundle* frameworkBundle = nil;
  778. static dispatch_once_t predicate;
  779. dispatch_once(&predicate, ^{
  780. NSString* mainBundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
  781. NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:@"Assets.bundle"];
  782. frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath];
  783. });
  784. return frameworkBundle;
  785. }
  786. @end