|
@@ -0,0 +1,918 @@
|
|
|
|
|
+// Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
|
+//
|
|
|
|
|
+// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
+// you may not use this file except in compliance with the License.
|
|
|
|
|
+// You may obtain a copy of the License at
|
|
|
|
|
+//
|
|
|
|
|
+// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+//
|
|
|
|
|
+// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
+// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
+// See the License for the specific language governing permissions and
|
|
|
|
|
+// limitations under the License.
|
|
|
|
|
+
|
|
|
|
|
+#import "YTPlayerView.h"
|
|
|
|
|
+
|
|
|
|
|
+// These are instances of NSString because we get them from parsing a URL. It would be silly to
|
|
|
|
|
+// convert these into an integer just to have to convert the URL query string value into an integer
|
|
|
|
|
+// as well for the sake of doing a value comparison. A full list of response error codes can be
|
|
|
|
|
+// found here:
|
|
|
|
|
+// https://developers.google.com/youtube/iframe_api_reference
|
|
|
|
|
+NSString static *const kYTPlayerStateUnstartedCode = @"-1";
|
|
|
|
|
+NSString static *const kYTPlayerStateEndedCode = @"0";
|
|
|
|
|
+NSString static *const kYTPlayerStatePlayingCode = @"1";
|
|
|
|
|
+NSString static *const kYTPlayerStatePausedCode = @"2";
|
|
|
|
|
+NSString static *const kYTPlayerStateBufferingCode = @"3";
|
|
|
|
|
+NSString static *const kYTPlayerStateCuedCode = @"5";
|
|
|
|
|
+NSString static *const kYTPlayerStateUnknownCode = @"unknown";
|
|
|
|
|
+
|
|
|
|
|
+// Constants representing playback quality.
|
|
|
|
|
+NSString static *const kYTPlaybackQualitySmallQuality = @"small";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityMediumQuality = @"medium";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityLargeQuality = @"large";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityHD720Quality = @"hd720";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityHD1080Quality = @"hd1080";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityHighResQuality = @"highres";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityAutoQuality = @"auto";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityDefaultQuality = @"default";
|
|
|
|
|
+NSString static *const kYTPlaybackQualityUnknownQuality = @"unknown";
|
|
|
|
|
+
|
|
|
|
|
+// Constants representing YouTube player errors.
|
|
|
|
|
+NSString static *const kYTPlayerErrorInvalidParamErrorCode = @"2";
|
|
|
|
|
+NSString static *const kYTPlayerErrorHTML5ErrorCode = @"5";
|
|
|
|
|
+NSString static *const kYTPlayerErrorVideoNotFoundErrorCode = @"100";
|
|
|
|
|
+NSString static *const kYTPlayerErrorNotEmbeddableErrorCode = @"101";
|
|
|
|
|
+NSString static *const kYTPlayerErrorCannotFindVideoErrorCode = @"105";
|
|
|
|
|
+NSString static *const kYTPlayerErrorSameAsNotEmbeddableErrorCode = @"150";
|
|
|
|
|
+
|
|
|
|
|
+// Constants representing player callbacks.
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnReady = @"onReady";
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnStateChange = @"onStateChange";
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange";
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnError = @"onError";
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnPlayTime = @"onPlayTime";
|
|
|
|
|
+
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIReady = @"onYouTubeIframeAPIReady";
|
|
|
|
|
+NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad = @"onYouTubeIframeAPIFailedToLoad";
|
|
|
|
|
+
|
|
|
|
|
+NSString static *const kYTPlayerEmbedUrlRegexPattern = @"^http(s)://(www.)youtube.com/embed/(.*)$";
|
|
|
|
|
+NSString static *const kYTPlayerAdUrlRegexPattern = @"^http(s)://pubads.g.doubleclick.net/pagead/conversion/";
|
|
|
|
|
+NSString static *const kYTPlayerOAuthRegexPattern = @"^http(s)://accounts.google.com/o/oauth2/(.*)$";
|
|
|
|
|
+NSString static *const kYTPlayerStaticProxyRegexPattern = @"^https://content.googleapis.com/static/proxy.html(.*)$";
|
|
|
|
|
+NSString static *const kYTPlayerSyndicationRegexPattern = @"^https://tpc.googlesyndication.com/sodar/(.*).html$";
|
|
|
|
|
+
|
|
|
|
|
+@interface YTPlayerView()
|
|
|
|
|
+
|
|
|
|
|
+@property (nonatomic, strong) NSURL *originURL;
|
|
|
|
|
+@property (nonatomic, weak) UIView *initialLoadingView;
|
|
|
|
|
+
|
|
|
|
|
+@end
|
|
|
|
|
+
|
|
|
|
|
+@implementation YTPlayerView
|
|
|
|
|
+
|
|
|
|
|
+- (BOOL)loadWithVideoId:(NSString *)videoId {
|
|
|
|
|
+ return [self loadWithVideoId:videoId playerVars:nil];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (BOOL)loadWithPlaylistId:(NSString *)playlistId {
|
|
|
|
|
+ return [self loadWithPlaylistId:playlistId playerVars:nil];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars {
|
|
|
|
|
+ if (!playerVars) {
|
|
|
|
|
+ playerVars = @{};
|
|
|
|
|
+ }
|
|
|
|
|
+ NSDictionary *playerParams = @{ @"videoId" : videoId, @"playerVars" : playerVars };
|
|
|
|
|
+ return [self loadWithPlayerParams:playerParams];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars {
|
|
|
|
|
+
|
|
|
|
|
+ // Mutable copy because we may have been passed an immutable config dictionary.
|
|
|
|
|
+ NSMutableDictionary *tempPlayerVars = [[NSMutableDictionary alloc] init];
|
|
|
|
|
+ [tempPlayerVars setValue:@"playlist" forKey:@"listType"];
|
|
|
|
|
+ [tempPlayerVars setValue:playlistId forKey:@"list"];
|
|
|
|
|
+ if (playerVars) {
|
|
|
|
|
+ [tempPlayerVars addEntriesFromDictionary:playerVars];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NSDictionary *playerParams = @{ @"playerVars" : tempPlayerVars };
|
|
|
|
|
+ return [self loadWithPlayerParams:playerParams];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Player methods
|
|
|
|
|
+
|
|
|
|
|
+- (void)playVideo {
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.playVideo();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)pauseVideo {
|
|
|
|
|
+ [self notifyDelegateOfYouTubeCallbackUrl:[NSURL URLWithString:[NSString stringWithFormat:@"ytplayer://onStateChange?data=%@", kYTPlayerStatePausedCode]]];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.pauseVideo();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)stopVideo {
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.stopVideo();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead {
|
|
|
|
|
+ NSNumber *secondsValue = [NSNumber numberWithFloat:seekToSeconds];
|
|
|
|
|
+ NSString *allowSeekAheadValue = [self stringForJSBoolean:allowSeekAhead];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, %@);", secondsValue, allowSeekAheadValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Cueing methods
|
|
|
|
|
+
|
|
|
|
|
+- (void)cueVideoById:(NSString *)videoId
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.cueVideoById('%@', %@, '%@');",
|
|
|
|
|
+ videoId, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)cueVideoById:(NSString *)videoId
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ endSeconds:(float)endSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.cueVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});", videoId, startSecondsValue, endSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadVideoById:(NSString *)videoId
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.loadVideoById('%@', %@, '%@');",
|
|
|
|
|
+ videoId, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadVideoById:(NSString *)videoId
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ endSeconds:(float)endSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.loadVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});",videoId, startSecondsValue, endSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)cueVideoByURL:(NSString *)videoURL
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, '%@');",
|
|
|
|
|
+ videoURL, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)cueVideoByURL:(NSString *)videoURL
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ endSeconds:(float)endSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, %@, '%@');",
|
|
|
|
|
+ videoURL, startSecondsValue, endSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadVideoByURL:(NSString *)videoURL
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, '%@');",
|
|
|
|
|
+ videoURL, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadVideoByURL:(NSString *)videoURL
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ endSeconds:(float)endSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, %@, '%@');",
|
|
|
|
|
+ videoURL, startSecondsValue, endSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Cueing methods for lists
|
|
|
|
|
+
|
|
|
|
|
+- (void)cuePlaylistByPlaylistId:(NSString *)playlistId
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId];
|
|
|
|
|
+ [self cuePlaylist:playlistIdString
|
|
|
|
|
+ index:index
|
|
|
|
|
+ startSeconds:startSeconds
|
|
|
|
|
+ suggestedQuality:suggestedQuality];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)cuePlaylistByVideos:(NSArray *)videoIds
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ [self cuePlaylist:[self stringFromVideoIdArray:videoIds]
|
|
|
|
|
+ index:index
|
|
|
|
|
+ startSeconds:startSeconds
|
|
|
|
|
+ suggestedQuality:suggestedQuality];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadPlaylistByPlaylistId:(NSString *)playlistId
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId];
|
|
|
|
|
+ [self loadPlaylist:playlistIdString
|
|
|
|
|
+ index:index
|
|
|
|
|
+ startSeconds:startSeconds
|
|
|
|
|
+ suggestedQuality:suggestedQuality];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)loadPlaylistByVideos:(NSArray *)videoIds
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ [self loadPlaylist:[self stringFromVideoIdArray:videoIds]
|
|
|
|
|
+ index:index
|
|
|
|
|
+ startSeconds:startSeconds
|
|
|
|
|
+ suggestedQuality:suggestedQuality];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Setting the playback rate
|
|
|
|
|
+
|
|
|
|
|
+- (float)playbackRate {
|
|
|
|
|
+ NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackRate();"];
|
|
|
|
|
+ return [returnValue floatValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setPlaybackRate:(float)suggestedRate {
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.setPlaybackRate(%f);", suggestedRate];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (NSArray *)availablePlaybackRates {
|
|
|
|
|
+ NSString *returnValue =
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.getAvailablePlaybackRates();"];
|
|
|
|
|
+
|
|
|
|
|
+ NSData *playbackRateData = [returnValue dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
|
+ NSError *jsonDeserializationError;
|
|
|
|
|
+ NSArray *playbackRates = [NSJSONSerialization JSONObjectWithData:playbackRateData
|
|
|
|
|
+ options:kNilOptions
|
|
|
|
|
+ error:&jsonDeserializationError];
|
|
|
|
|
+ if (jsonDeserializationError) {
|
|
|
|
|
+ return nil;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return playbackRates;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Setting playback behavior for playlists
|
|
|
|
|
+
|
|
|
|
|
+- (void)setLoop:(BOOL)loop {
|
|
|
|
|
+ NSString *loopPlayListValue = [self stringForJSBoolean:loop];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.setLoop(%@);", loopPlayListValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setShuffle:(BOOL)shuffle {
|
|
|
|
|
+ NSString *shufflePlayListValue = [self stringForJSBoolean:shuffle];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.setShuffle(%@);", shufflePlayListValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Playback status
|
|
|
|
|
+
|
|
|
|
|
+- (float)videoLoadedFraction {
|
|
|
|
|
+ return [[self stringFromEvaluatingJavaScript:@"player.getVideoLoadedFraction();"] floatValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (YTPlayerState)playerState {
|
|
|
|
|
+ NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlayerState();"];
|
|
|
|
|
+ return [YTPlayerView playerStateForString:returnValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (float)currentTime {
|
|
|
|
|
+ return [[self stringFromEvaluatingJavaScript:@"player.getCurrentTime();"] floatValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Playback quality
|
|
|
|
|
+- (YTPlaybackQuality)playbackQuality {
|
|
|
|
|
+ NSString *qualityValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackQuality();"];
|
|
|
|
|
+ return [YTPlayerView playbackQualityForString:qualityValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.setPlaybackQuality('%@');", qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Video information methods
|
|
|
|
|
+
|
|
|
|
|
+- (NSTimeInterval)duration {
|
|
|
|
|
+ return [[self stringFromEvaluatingJavaScript:@"player.getDuration();"] doubleValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (NSURL *)videoUrl {
|
|
|
|
|
+ return [NSURL URLWithString:[self stringFromEvaluatingJavaScript:@"player.getVideoUrl();"]];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (NSString *)videoEmbedCode {
|
|
|
|
|
+ return [self stringFromEvaluatingJavaScript:@"player.getVideoEmbedCode();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Playlist methods
|
|
|
|
|
+
|
|
|
|
|
+- (NSArray *)playlist {
|
|
|
|
|
+ NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylist();"];
|
|
|
|
|
+
|
|
|
|
|
+ NSData *playlistData = [returnValue dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
|
+ NSError *jsonDeserializationError;
|
|
|
|
|
+ NSArray *videoIds = [NSJSONSerialization JSONObjectWithData:playlistData
|
|
|
|
|
+ options:kNilOptions
|
|
|
|
|
+ error:&jsonDeserializationError];
|
|
|
|
|
+ if (jsonDeserializationError) {
|
|
|
|
|
+ return nil;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return videoIds;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (int)playlistIndex {
|
|
|
|
|
+ NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylistIndex();"];
|
|
|
|
|
+ return [returnValue intValue];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Playing a video in a playlist
|
|
|
|
|
+
|
|
|
|
|
+- (void)nextVideo {
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.nextVideo();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)previousVideo {
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.previousVideo();"];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)playVideoAt:(int)index {
|
|
|
|
|
+ NSString *command =
|
|
|
|
|
+ [NSString stringWithFormat:@"player.playVideoAt(%@);", [NSNumber numberWithInt:index]];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Helper methods
|
|
|
|
|
+
|
|
|
|
|
+- (NSArray *)availableQualityLevels {
|
|
|
|
|
+ NSString *returnValue =
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:@"player.getAvailableQualityLevels().toString();"];
|
|
|
|
|
+ if(!returnValue) return nil;
|
|
|
|
|
+
|
|
|
|
|
+ NSArray *rawQualityValues = [returnValue componentsSeparatedByString:@","];
|
|
|
|
|
+ NSMutableArray *levels = [[NSMutableArray alloc] init];
|
|
|
|
|
+ for (NSString *rawQualityValue in rawQualityValues) {
|
|
|
|
|
+ YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:rawQualityValue];
|
|
|
|
|
+ [levels addObject:[NSNumber numberWithInt:quality]];
|
|
|
|
|
+ }
|
|
|
|
|
+ return levels;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (BOOL)webView:(UIWebView *)webView
|
|
|
|
|
+ shouldStartLoadWithRequest:(NSURLRequest *)request
|
|
|
|
|
+ navigationType:(UIWebViewNavigationType)navigationType {
|
|
|
|
|
+ if ([request.URL.host isEqual: self.originURL.host]) {
|
|
|
|
|
+ return YES;
|
|
|
|
|
+ } else if ([request.URL.scheme isEqual:@"ytplayer"]) {
|
|
|
|
|
+ [self notifyDelegateOfYouTubeCallbackUrl:request.URL];
|
|
|
|
|
+ return NO;
|
|
|
|
|
+ } else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) {
|
|
|
|
|
+ return [self handleHttpNavigationToUrl:request.URL];
|
|
|
|
|
+ }
|
|
|
|
|
+ return YES;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
|
|
|
|
|
+ if (self.initialLoadingView) {
|
|
|
|
|
+ [self.initialLoadingView removeFromSuperview];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Convert a quality value from NSString to the typed enum value.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param qualityString A string representing playback quality. Ex: "small", "medium", "hd1080".
|
|
|
|
|
+ * @return An enum value representing the playback quality.
|
|
|
|
|
+ */
|
|
|
|
|
++ (YTPlaybackQuality)playbackQualityForString:(NSString *)qualityString {
|
|
|
|
|
+ YTPlaybackQuality quality = kYTPlaybackQualityUnknown;
|
|
|
|
|
+
|
|
|
|
|
+ if ([qualityString isEqualToString:kYTPlaybackQualitySmallQuality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualitySmall;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityMediumQuality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityMedium;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityLargeQuality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityLarge;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityHD720Quality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityHD720;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityHD1080Quality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityHD1080;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityHighResQuality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityHighRes;
|
|
|
|
|
+ } else if ([qualityString isEqualToString:kYTPlaybackQualityAutoQuality]) {
|
|
|
|
|
+ quality = kYTPlaybackQualityAuto;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return quality;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Convert a |YTPlaybackQuality| value from the typed value to NSString.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param quality A |YTPlaybackQuality| parameter.
|
|
|
|
|
+ * @return An |NSString| value to be used in the JavaScript bridge.
|
|
|
|
|
+ */
|
|
|
|
|
++ (NSString *)stringForPlaybackQuality:(YTPlaybackQuality)quality {
|
|
|
|
|
+ switch (quality) {
|
|
|
|
|
+ case kYTPlaybackQualitySmall:
|
|
|
|
|
+ return kYTPlaybackQualitySmallQuality;
|
|
|
|
|
+ case kYTPlaybackQualityMedium:
|
|
|
|
|
+ return kYTPlaybackQualityMediumQuality;
|
|
|
|
|
+ case kYTPlaybackQualityLarge:
|
|
|
|
|
+ return kYTPlaybackQualityLargeQuality;
|
|
|
|
|
+ case kYTPlaybackQualityHD720:
|
|
|
|
|
+ return kYTPlaybackQualityHD720Quality;
|
|
|
|
|
+ case kYTPlaybackQualityHD1080:
|
|
|
|
|
+ return kYTPlaybackQualityHD1080Quality;
|
|
|
|
|
+ case kYTPlaybackQualityHighRes:
|
|
|
|
|
+ return kYTPlaybackQualityHighResQuality;
|
|
|
|
|
+ case kYTPlaybackQualityAuto:
|
|
|
|
|
+ return kYTPlaybackQualityAutoQuality;
|
|
|
|
|
+ default:
|
|
|
|
|
+ return kYTPlaybackQualityUnknownQuality;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Convert a state value from NSString to the typed enum value.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param stateString A string representing player state. Ex: "-1", "0", "1".
|
|
|
|
|
+ * @return An enum value representing the player state.
|
|
|
|
|
+ */
|
|
|
|
|
++ (YTPlayerState)playerStateForString:(NSString *)stateString {
|
|
|
|
|
+ YTPlayerState state = kYTPlayerStateUnknown;
|
|
|
|
|
+ if ([stateString isEqualToString:kYTPlayerStateUnstartedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateUnstarted;
|
|
|
|
|
+ } else if ([stateString isEqualToString:kYTPlayerStateEndedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateEnded;
|
|
|
|
|
+ } else if ([stateString isEqualToString:kYTPlayerStatePlayingCode]) {
|
|
|
|
|
+ state = kYTPlayerStatePlaying;
|
|
|
|
|
+ } else if ([stateString isEqualToString:kYTPlayerStatePausedCode]) {
|
|
|
|
|
+ state = kYTPlayerStatePaused;
|
|
|
|
|
+ } else if ([stateString isEqualToString:kYTPlayerStateBufferingCode]) {
|
|
|
|
|
+ state = kYTPlayerStateBuffering;
|
|
|
|
|
+ } else if ([stateString isEqualToString:kYTPlayerStateCuedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateQueued;
|
|
|
|
|
+ }
|
|
|
|
|
+ return state;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Convert a state value from the typed value to NSString.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param quality A |YTPlayerState| parameter.
|
|
|
|
|
+ * @return A string value to be used in the JavaScript bridge.
|
|
|
|
|
+ */
|
|
|
|
|
++ (NSString *)stringForPlayerState:(YTPlayerState)state {
|
|
|
|
|
+ switch (state) {
|
|
|
|
|
+ case kYTPlayerStateUnstarted:
|
|
|
|
|
+ return kYTPlayerStateUnstartedCode;
|
|
|
|
|
+ case kYTPlayerStateEnded:
|
|
|
|
|
+ return kYTPlayerStateEndedCode;
|
|
|
|
|
+ case kYTPlayerStatePlaying:
|
|
|
|
|
+ return kYTPlayerStatePlayingCode;
|
|
|
|
|
+ case kYTPlayerStatePaused:
|
|
|
|
|
+ return kYTPlayerStatePausedCode;
|
|
|
|
|
+ case kYTPlayerStateBuffering:
|
|
|
|
|
+ return kYTPlayerStateBufferingCode;
|
|
|
|
|
+ case kYTPlayerStateQueued:
|
|
|
|
|
+ return kYTPlayerStateCuedCode;
|
|
|
|
|
+ default:
|
|
|
|
|
+ return kYTPlayerStateUnknownCode;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Private methods
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private method to handle "navigation" to a callback URL of the format
|
|
|
|
|
+ * ytplayer://action?data=someData
|
|
|
|
|
+ * This is how the UIWebView communicates with the containing Objective-C code.
|
|
|
|
|
+ * Side effects of this method are that it calls methods on this class's delegate.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param url A URL of the format ytplayer://action?data=value.
|
|
|
|
|
+ */
|
|
|
|
|
+- (void)notifyDelegateOfYouTubeCallbackUrl: (NSURL *) url {
|
|
|
|
|
+ NSString *action = url.host;
|
|
|
|
|
+
|
|
|
|
|
+ NSLog(@"action:%@",action);
|
|
|
|
|
+
|
|
|
|
|
+ // We know the query can only be of the format ytplayer://action?data=SOMEVALUE,
|
|
|
|
|
+ // so we parse out the value.
|
|
|
|
|
+ NSString *query = url.query;
|
|
|
|
|
+ NSString *data;
|
|
|
|
|
+ if (query) {
|
|
|
|
|
+ data = [query componentsSeparatedByString:@"="][1];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ([action isEqual:kYTPlayerCallbackOnReady]) {
|
|
|
|
|
+ if (self.initialLoadingView) {
|
|
|
|
|
+ [self.initialLoadingView removeFromSuperview];
|
|
|
|
|
+ }
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerViewDidBecomeReady:)]) {
|
|
|
|
|
+ [self.delegate playerViewDidBecomeReady:self];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ([action isEqual:kYTPlayerCallbackOnStateChange]) {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerView:didChangeToState:)]) {
|
|
|
|
|
+ YTPlayerState state = kYTPlayerStateUnknown;
|
|
|
|
|
+
|
|
|
|
|
+ if ([data isEqual:kYTPlayerStateEndedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateEnded;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerStatePlayingCode]) {
|
|
|
|
|
+ state = kYTPlayerStatePlaying;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerStatePausedCode]) {
|
|
|
|
|
+ state = kYTPlayerStatePaused;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerStateBufferingCode]) {
|
|
|
|
|
+ state = kYTPlayerStateBuffering;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerStateCuedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateQueued;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerStateUnstartedCode]) {
|
|
|
|
|
+ state = kYTPlayerStateUnstarted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [self.delegate playerView:self didChangeToState:state];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ([action isEqual:kYTPlayerCallbackOnPlaybackQualityChange]) {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerView:didChangeToQuality:)]) {
|
|
|
|
|
+ YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:data];
|
|
|
|
|
+ [self.delegate playerView:self didChangeToQuality:quality];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ([action isEqual:kYTPlayerCallbackOnError]) {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerView:receivedError:)]) {
|
|
|
|
|
+ YTPlayerError error = kYTPlayerErrorUnknown;
|
|
|
|
|
+
|
|
|
|
|
+ if ([data isEqual:kYTPlayerErrorInvalidParamErrorCode]) {
|
|
|
|
|
+ error = kYTPlayerErrorInvalidParam;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerErrorHTML5ErrorCode]) {
|
|
|
|
|
+ error = kYTPlayerErrorHTML5Error;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerErrorNotEmbeddableErrorCode] ||
|
|
|
|
|
+ [data isEqual:kYTPlayerErrorSameAsNotEmbeddableErrorCode]) {
|
|
|
|
|
+ error = kYTPlayerErrorNotEmbeddable;
|
|
|
|
|
+ } else if ([data isEqual:kYTPlayerErrorVideoNotFoundErrorCode] ||
|
|
|
|
|
+ [data isEqual:kYTPlayerErrorCannotFindVideoErrorCode]) {
|
|
|
|
|
+ error = kYTPlayerErrorVideoNotFound;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [self.delegate playerView:self receivedError:error];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ([action isEqualToString:kYTPlayerCallbackOnPlayTime]) {
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerView:didPlayTime:)]) {
|
|
|
|
|
+ float time = [data floatValue];
|
|
|
|
|
+ [self.delegate playerView:self didPlayTime:time];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if ([action isEqualToString:kYTPlayerCallbackOnYouTubeIframeAPIFailedToLoad]) {
|
|
|
|
|
+ if (self.initialLoadingView) {
|
|
|
|
|
+ [self.initialLoadingView removeFromSuperview];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else if ([action isEqualToString:@"restriction"])
|
|
|
|
|
+ {
|
|
|
|
|
+ self.webView.hidden = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ int a=0;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+-(NSString*) Embed2VID:(NSString*) iframe_embed
|
|
|
|
|
+{
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ NSString* content = iframe_embed;
|
|
|
|
|
+ NSString* pattern = @"(\\w+):\\/\\/([^/:]+)(:\\d*)?([^# ]*\\b)";
|
|
|
|
|
+
|
|
|
|
|
+ NSLog(@"content: %@",content);
|
|
|
|
|
+ NSLog(@"pattern: %@",pattern);
|
|
|
|
|
+
|
|
|
|
|
+ if(content==nil||pattern==nil)
|
|
|
|
|
+ return nil;
|
|
|
|
|
+
|
|
|
|
|
+ NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:nil error:nil];
|
|
|
|
|
+
|
|
|
|
|
+ NSTextCheckingResult *match = [regex firstMatchInString:content options:nil range:NSMakeRange(0, content.length)];
|
|
|
|
|
+
|
|
|
|
|
+ int i=0;
|
|
|
|
|
+ NSLog(@"%d->range with richtext label: %d,%d",i, [match rangeAtIndex:i].location,[match rangeAtIndex:i].length);
|
|
|
|
|
+ // NSLog(@"%d-> range of text: %d,%d ",i+1, [match rangeAtIndex:i+1].location,[match rangeAtIndex:i+1].length);
|
|
|
|
|
+ NSLog(@"text: %@ ",[content substringWithRange:[match rangeAtIndex:i]]);
|
|
|
|
|
+
|
|
|
|
|
+ return [[content substringWithRange:[match rangeAtIndex:i]] lastPathComponent];
|
|
|
|
|
+}
|
|
|
|
|
+- (BOOL)handleHttpNavigationToUrl:(NSURL *) url {
|
|
|
|
|
+ // Usually this means the user has clicked on the YouTube logo or an error message in the
|
|
|
|
|
+ // player. Most URLs should open in the browser. The only http(s) URL that should open in this
|
|
|
|
|
+ // UIWebView is the URL for the embed, which is of the format:
|
|
|
|
|
+ // http(s)://www.youtube.com/embed/[VIDEO ID]?[PARAMETERS]
|
|
|
|
|
+ NSError *error = NULL;
|
|
|
|
|
+ NSRegularExpression *ytRegex =
|
|
|
|
|
+ [NSRegularExpression regularExpressionWithPattern:kYTPlayerEmbedUrlRegexPattern
|
|
|
|
|
+ options:NSRegularExpressionCaseInsensitive
|
|
|
|
|
+ error:&error];
|
|
|
|
|
+ NSTextCheckingResult *ytMatch =
|
|
|
|
|
+ [ytRegex firstMatchInString:url.absoluteString
|
|
|
|
|
+ options:0
|
|
|
|
|
+ range:NSMakeRange(0, [url.absoluteString length])];
|
|
|
|
|
+
|
|
|
|
|
+ NSRegularExpression *adRegex =
|
|
|
|
|
+ [NSRegularExpression regularExpressionWithPattern:kYTPlayerAdUrlRegexPattern
|
|
|
|
|
+ options:NSRegularExpressionCaseInsensitive
|
|
|
|
|
+ error:&error];
|
|
|
|
|
+ NSTextCheckingResult *adMatch =
|
|
|
|
|
+ [adRegex firstMatchInString:url.absoluteString
|
|
|
|
|
+ options:0
|
|
|
|
|
+ range:NSMakeRange(0, [url.absoluteString length])];
|
|
|
|
|
+
|
|
|
|
|
+ NSRegularExpression *syndicationRegex =
|
|
|
|
|
+ [NSRegularExpression regularExpressionWithPattern:kYTPlayerSyndicationRegexPattern
|
|
|
|
|
+ options:NSRegularExpressionCaseInsensitive
|
|
|
|
|
+ error:&error];
|
|
|
|
|
+
|
|
|
|
|
+ NSTextCheckingResult *syndicationMatch =
|
|
|
|
|
+ [syndicationRegex firstMatchInString:url.absoluteString
|
|
|
|
|
+ options:0
|
|
|
|
|
+ range:NSMakeRange(0, [url.absoluteString length])];
|
|
|
|
|
+
|
|
|
|
|
+ NSRegularExpression *oauthRegex =
|
|
|
|
|
+ [NSRegularExpression regularExpressionWithPattern:kYTPlayerOAuthRegexPattern
|
|
|
|
|
+ options:NSRegularExpressionCaseInsensitive
|
|
|
|
|
+ error:&error];
|
|
|
|
|
+ NSTextCheckingResult *oauthMatch =
|
|
|
|
|
+ [oauthRegex firstMatchInString:url.absoluteString
|
|
|
|
|
+ options:0
|
|
|
|
|
+ range:NSMakeRange(0, [url.absoluteString length])];
|
|
|
|
|
+
|
|
|
|
|
+ NSRegularExpression *staticProxyRegex =
|
|
|
|
|
+ [NSRegularExpression regularExpressionWithPattern:kYTPlayerStaticProxyRegexPattern
|
|
|
|
|
+ options:NSRegularExpressionCaseInsensitive
|
|
|
|
|
+ error:&error];
|
|
|
|
|
+ NSTextCheckingResult *staticProxyMatch =
|
|
|
|
|
+ [staticProxyRegex firstMatchInString:url.absoluteString
|
|
|
|
|
+ options:0
|
|
|
|
|
+ range:NSMakeRange(0, [url.absoluteString length])];
|
|
|
|
|
+
|
|
|
|
|
+ if (ytMatch || adMatch || oauthMatch || staticProxyMatch || syndicationMatch) {
|
|
|
|
|
+ return YES;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ [[UIApplication sharedApplication] openURL:url];
|
|
|
|
|
+ return NO;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private helper method to load an iframe player with the given player parameters.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters
|
|
|
|
|
+ * to instantiate the HTML5 player with. This differs depending on
|
|
|
|
|
+ * whether a single video or playlist is being loaded.
|
|
|
|
|
+ * @return YES if successful, NO if not.
|
|
|
|
|
+ */
|
|
|
|
|
+- (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams {
|
|
|
|
|
+ NSDictionary *playerCallbacks = @{
|
|
|
|
|
+ @"onReady" : @"onReady",
|
|
|
|
|
+ @"onStateChange" : @"onStateChange",
|
|
|
|
|
+ @"onPlaybackQualityChange" : @"onPlaybackQualityChange",
|
|
|
|
|
+ @"onError" : @"onPlayerError"
|
|
|
|
|
+ };
|
|
|
|
|
+ NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init];
|
|
|
|
|
+ if (additionalPlayerParams) {
|
|
|
|
|
+ [playerParams addEntriesFromDictionary:additionalPlayerParams];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (![playerParams objectForKey:@"height"]) {
|
|
|
|
|
+ [playerParams setValue:@"100%" forKey:@"height"];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (![playerParams objectForKey:@"width"]) {
|
|
|
|
|
+ [playerParams setValue:@"100%" forKey:@"width"];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [playerParams setValue:playerCallbacks forKey:@"events"];
|
|
|
|
|
+
|
|
|
|
|
+ if ([playerParams objectForKey:@"playerVars"]) {
|
|
|
|
|
+ NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init];
|
|
|
|
|
+ [playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]];
|
|
|
|
|
+
|
|
|
|
|
+ if (![playerVars objectForKey:@"origin"]) {
|
|
|
|
|
+ self.originURL = [NSURL URLWithString:@"about:blank"];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.originURL = [NSURL URLWithString: [playerVars objectForKey:@"origin"]];
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // This must not be empty so we can render a '{}' in the output JSON
|
|
|
|
|
+ [playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Remove the existing webView to reset any state
|
|
|
|
|
+ [self.webView removeFromSuperview];
|
|
|
|
|
+ _webView = [self createNewWebView];
|
|
|
|
|
+ [self addSubview:self.webView];
|
|
|
|
|
+
|
|
|
|
|
+ NSError *error = nil;
|
|
|
|
|
+ NSString *path = [[NSBundle bundleForClass:[YTPlayerView class]] pathForResource:@"YTPlayerView-iframe-player"
|
|
|
|
|
+ ofType:@"html"
|
|
|
|
|
+ inDirectory:@"Assets"];
|
|
|
|
|
+
|
|
|
|
|
+ // in case of using Swift and embedded frameworks, resources included not in main bundle,
|
|
|
|
|
+ // but in framework bundle
|
|
|
|
|
+ if (!path) {
|
|
|
|
|
+ path = [[[self class] frameworkBundle] pathForResource:@"YTPlayerView-iframe-player"
|
|
|
|
|
+ ofType:@"html"
|
|
|
|
|
+ inDirectory:@"Assets"];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NSString *embedHTMLTemplate =
|
|
|
|
|
+ [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
|
|
|
|
|
+
|
|
|
|
|
+ if (error) {
|
|
|
|
|
+ NSLog(@"Received error rendering template: %@", error);
|
|
|
|
|
+ return NO;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Render the playerVars as a JSON dictionary.
|
|
|
|
|
+ NSError *jsonRenderingError = nil;
|
|
|
|
|
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams
|
|
|
|
|
+ options:NSJSONWritingPrettyPrinted
|
|
|
|
|
+ error:&jsonRenderingError];
|
|
|
|
|
+ if (jsonRenderingError) {
|
|
|
|
|
+ NSLog(@"Attempted configuration of player with invalid playerVars: %@ \tError: %@",
|
|
|
|
|
+ playerParams,
|
|
|
|
|
+ jsonRenderingError);
|
|
|
|
|
+ return NO;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NSString *playerVarsJsonString =
|
|
|
|
|
+ [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
|
|
|
|
|
+
|
|
|
|
|
+ NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString];
|
|
|
|
|
+ [self.webView loadHTMLString:embedHTML baseURL: self.originURL];
|
|
|
|
|
+ [self.webView setDelegate:self];
|
|
|
|
|
+ self.webView.allowsInlineMediaPlayback = YES;
|
|
|
|
|
+ self.webView.mediaPlaybackRequiresUserAction = NO;
|
|
|
|
|
+
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerViewPreferredInitialLoadingView:)]) {
|
|
|
|
|
+ UIView *initialLoadingView = [self.delegate playerViewPreferredInitialLoadingView:self];
|
|
|
|
|
+ if (initialLoadingView) {
|
|
|
|
|
+ initialLoadingView.frame = self.bounds;
|
|
|
|
|
+ initialLoadingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
|
|
|
+ [self addSubview:initialLoadingView];
|
|
|
|
|
+ self.initialLoadingView = initialLoadingView;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return YES;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private method for cueing both cases of playlist ID and array of video IDs. Cueing
|
|
|
|
|
+ * a playlist does not start playback.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param cueingString A JavaScript string representing an array, playlist ID or list of
|
|
|
|
|
+ * video IDs to play with the playlist player.
|
|
|
|
|
+ * @param index 0-index position of video to start playback on.
|
|
|
|
|
+ * @param startSeconds Seconds after start of video to begin playback.
|
|
|
|
|
+ * @param suggestedQuality Suggested YTPlaybackQuality to play the videos.
|
|
|
|
|
+ * @return The result of cueing the playlist.
|
|
|
|
|
+ */
|
|
|
|
|
+- (void)cuePlaylist:(NSString *)cueingString
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *indexValue = [NSNumber numberWithInt:index];
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.cuePlaylist(%@, %@, %@, '%@');",
|
|
|
|
|
+ cueingString, indexValue, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private method for loading both cases of playlist ID and array of video IDs. Loading
|
|
|
|
|
+ * a playlist automatically starts playback.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param cueingString A JavaScript string representing an array, playlist ID or list of
|
|
|
|
|
+ * video IDs to play with the playlist player.
|
|
|
|
|
+ * @param index 0-index position of video to start playback on.
|
|
|
|
|
+ * @param startSeconds Seconds after start of video to begin playback.
|
|
|
|
|
+ * @param suggestedQuality Suggested YTPlaybackQuality to play the videos.
|
|
|
|
|
+ * @return The result of cueing the playlist.
|
|
|
|
|
+ */
|
|
|
|
|
+- (void)loadPlaylist:(NSString *)cueingString
|
|
|
|
|
+ index:(int)index
|
|
|
|
|
+ startSeconds:(float)startSeconds
|
|
|
|
|
+ suggestedQuality:(YTPlaybackQuality)suggestedQuality {
|
|
|
|
|
+ NSNumber *indexValue = [NSNumber numberWithInt:index];
|
|
|
|
|
+ NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds];
|
|
|
|
|
+ NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality];
|
|
|
|
|
+ NSString *command = [NSString stringWithFormat:@"player.loadPlaylist(%@, %@, %@, '%@');",
|
|
|
|
|
+ cueingString, indexValue, startSecondsValue, qualityValue];
|
|
|
|
|
+ [self stringFromEvaluatingJavaScript:command];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private helper method for converting an NSArray of video IDs into its JavaScript equivalent.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param videoIds An array of video ID strings to convert into JavaScript format.
|
|
|
|
|
+ * @return A JavaScript array in String format containing video IDs.
|
|
|
|
|
+ */
|
|
|
|
|
+- (NSString *)stringFromVideoIdArray:(NSArray *)videoIds {
|
|
|
|
|
+ NSMutableArray *formattedVideoIds = [[NSMutableArray alloc] init];
|
|
|
|
|
+
|
|
|
|
|
+ for (id unformattedId in videoIds) {
|
|
|
|
|
+ [formattedVideoIds addObject:[NSString stringWithFormat:@"'%@'", unformattedId]];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [NSString stringWithFormat:@"[%@]", [formattedVideoIds componentsJoinedByString:@", "]];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private method for evaluating JavaScript in the WebView.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param jsToExecute The JavaScript code in string format that we want to execute.
|
|
|
|
|
+ * @return JavaScript response from evaluating code.
|
|
|
|
|
+ */
|
|
|
|
|
+- (NSString *)stringFromEvaluatingJavaScript:(NSString *)jsToExecute {
|
|
|
|
|
+ return [self.webView stringByEvaluatingJavaScriptFromString:jsToExecute];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Private method to convert a Objective-C BOOL value to JS boolean value.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param boolValue Objective-C BOOL value.
|
|
|
|
|
+ * @return JavaScript Boolean value, i.e. "true" or "false".
|
|
|
|
|
+ */
|
|
|
|
|
+- (NSString *)stringForJSBoolean:(BOOL)boolValue {
|
|
|
|
|
+ return boolValue ? @"true" : @"false";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Exposed for Testing
|
|
|
|
|
+
|
|
|
|
|
+- (void)setWebView:(UIWebView *)webView {
|
|
|
|
|
+ _webView = webView;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (UIWebView *)createNewWebView {
|
|
|
|
|
+ UIWebView *webView = [[UIWebView alloc] initWithFrame:self.bounds];
|
|
|
|
|
+ webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
|
|
|
|
|
+ webView.scrollView.scrollEnabled = NO;
|
|
|
|
|
+ webView.scrollView.bounces = NO;
|
|
|
|
|
+
|
|
|
|
|
+ if ([self.delegate respondsToSelector:@selector(playerViewPreferredWebViewBackgroundColor:)]) {
|
|
|
|
|
+ webView.backgroundColor = [self.delegate playerViewPreferredWebViewBackgroundColor:self];
|
|
|
|
|
+ if (webView.backgroundColor == [UIColor clearColor]) {
|
|
|
|
|
+ webView.opaque = NO;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return webView;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)removeWebView {
|
|
|
|
|
+ [self.webView removeFromSuperview];
|
|
|
|
|
+ self.webView = nil;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
++ (NSBundle *)frameworkBundle {
|
|
|
|
|
+ static NSBundle* frameworkBundle = nil;
|
|
|
|
|
+ static dispatch_once_t predicate;
|
|
|
|
|
+ dispatch_once(&predicate, ^{
|
|
|
|
|
+ NSString* mainBundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
|
|
|
|
|
+ NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:@"Assets.bundle"];
|
|
|
|
|
+ frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath];
|
|
|
|
|
+ });
|
|
|
|
|
+ return frameworkBundle;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@end
|