├── Assets ├── YTPlayerView-iframe-player.html ├── YTPlayerView.h └── YTPlayerView.m ├── README.md ├── YTDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ ├── gabriel.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── simon.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── gabriel.xcuserdatad │ └── xcschemes │ │ ├── YTDemo.xcscheme │ │ └── xcschememanagement.plist │ └── simon.xcuserdatad │ └── xcschemes │ ├── YTDemo.xcscheme │ └── xcschememanagement.plist ├── YTDemo ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── PlayerViewController.swift ├── ViewController.swift └── YTDemo-Bridging-Header.h ├── YTDemoTests ├── Info.plist └── YTDemoTests.swift ├── YTPlayerView.h └── YTPlayerView.m /Assets/YTPlayerView-iframe-player.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 33 | 34 | 35 |
36 |
37 |
38 | 39 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /Assets/YTPlayerView.h: -------------------------------------------------------------------------------- 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 | 15 | #import 16 | 17 | @class YTPlayerView; 18 | 19 | /** These enums represent the state of the current video in the player. */ 20 | typedef NS_ENUM(NSInteger, YTPlayerState) { 21 | kYTPlayerStateUnstarted, 22 | kYTPlayerStateEnded, 23 | kYTPlayerStatePlaying, 24 | kYTPlayerStatePaused, 25 | kYTPlayerStateBuffering, 26 | kYTPlayerStateQueued, 27 | kYTPlayerStateUnknown 28 | }; 29 | 30 | /** These enums represent the resolution of the currently loaded video. */ 31 | typedef NS_ENUM(NSInteger, YTPlaybackQuality) { 32 | kYTPlaybackQualitySmall, 33 | kYTPlaybackQualityMedium, 34 | kYTPlaybackQualityLarge, 35 | kYTPlaybackQualityHD720, 36 | kYTPlaybackQualityHD1080, 37 | kYTPlaybackQualityHighRes, 38 | kYTPlaybackQualityAuto, /** Addition for YouTube Live Events. */ 39 | kYTPlaybackQualityDefault, 40 | kYTPlaybackQualityUnknown /** This should never be returned. It is here for future proofing. */ 41 | }; 42 | 43 | /** These enums represent error codes thrown by the player. */ 44 | typedef NS_ENUM(NSInteger, YTPlayerError) { 45 | kYTPlayerErrorInvalidParam, 46 | kYTPlayerErrorHTML5Error, 47 | kYTPlayerErrorVideoNotFound, // Functionally equivalent error codes 100 and 48 | // 105 have been collapsed into |kYTPlayerErrorVideoNotFound|. 49 | kYTPlayerErrorNotEmbeddable, // Functionally equivalent error codes 101 and 50 | // 150 have been collapsed into |kYTPlayerErrorNotEmbeddable|. 51 | kYTPlayerErrorUnknown 52 | }; 53 | 54 | /** 55 | * A delegate for ViewControllers to respond to YouTube player events outside 56 | * of the view, such as changes to video playback state or playback errors. 57 | * The callback functions correlate to the events fired by the IFrame API. 58 | * For the full documentation, see the IFrame documentation here: 59 | * https://developers.google.com/youtube/iframe_api_reference#Events 60 | */ 61 | @protocol YTPlayerViewDelegate 62 | 63 | @optional 64 | /** 65 | * Invoked when the player view is ready to receive API calls. 66 | * 67 | * @param playerView The YTPlayerView instance that has become ready. 68 | */ 69 | - (void)playerViewDidBecomeReady:(YTPlayerView *)playerView; 70 | 71 | /** 72 | * Callback invoked when player state has changed, e.g. stopped or started playback. 73 | * 74 | * @param playerView The YTPlayerView instance where playback state has changed. 75 | * @param state YTPlayerState designating the new playback state. 76 | */ 77 | - (void)playerView:(YTPlayerView *)playerView didChangeToState:(YTPlayerState)state; 78 | 79 | /** 80 | * Callback invoked when playback quality has changed. 81 | * 82 | * @param playerView The YTPlayerView instance where playback quality has changed. 83 | * @param quality YTPlaybackQuality designating the new playback quality. 84 | */ 85 | - (void)playerView:(YTPlayerView *)playerView didChangeToQuality:(YTPlaybackQuality)quality; 86 | 87 | /** 88 | * Callback invoked when an error has occured. 89 | * 90 | * @param playerView The YTPlayerView instance where the error has occurred. 91 | * @param error YTPlayerError containing the error state. 92 | */ 93 | - (void)playerView:(YTPlayerView *)playerView receivedError:(YTPlayerError)error; 94 | 95 | 96 | /** 97 | * Callback invoked frequently when playBack is plaing. 98 | * 99 | * @param playerView The YTPlayerView instance where the error has occurred. 100 | * @param playTime float containing curretn playback time. 101 | */ 102 | - (void)playerView:(YTPlayerView *)playerView didPlayTime:(float)playTime; 103 | 104 | @end 105 | 106 | /** 107 | * YTPlayerView is a custom UIView that client developers will use to include YouTube 108 | * videos in their iOS applications. It can be instantiated programmatically, or via 109 | * Interface Builder. Use the methods YTPlayerView::loadWithVideoId:, 110 | * YTPlayerView::loadWithPlaylistId: or their variants to set the video or playlist 111 | * to populate the view with. 112 | */ 113 | @interface YTPlayerView : UIView 114 | 115 | @property(nonatomic, strong, readonly) UIWebView *webView; 116 | 117 | /** A delegate to be notified on playback events. */ 118 | @property(nonatomic, weak) id delegate; 119 | 120 | /** 121 | * This method loads the player with the given video ID. 122 | * This is a convenience method for calling YTPlayerView::loadPlayerWithVideoId:withPlayerVars: 123 | * without player variables. 124 | * 125 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 126 | * To change the currently loaded video without reloading the entire UIWebView, use the 127 | * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. 128 | * 129 | * @param videoId The YouTube video ID of the video to load in the player view. 130 | * @return YES if player has been configured correctly, NO otherwise. 131 | */ 132 | - (BOOL)loadWithVideoId:(NSString *)videoId; 133 | 134 | /** 135 | * This method loads the player with the given playlist ID. 136 | * This is a convenience method for calling YTPlayerView::loadWithPlaylistId:withPlayerVars: 137 | * without player variables. 138 | * 139 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 140 | * To change the currently loaded video without reloading the entire UIWebView, use the 141 | * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: 142 | * family of methods. 143 | * 144 | * @param playlistId The YouTube playlist ID of the playlist to load in the player view. 145 | * @return YES if player has been configured correctly, NO otherwise. 146 | */ 147 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId; 148 | 149 | /** 150 | * This method loads the player with the given video ID and player variables. Player variables 151 | * specify optional parameters for video playback. For instance, to play a YouTube 152 | * video inline, the following playerVars dictionary would be used: 153 | * 154 | * @code 155 | * @{ @"playsinline" : @1 }; 156 | * @endcode 157 | * 158 | * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), 159 | * both strings and integers are valid values. The full list of parameters is defined at: 160 | * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. 161 | * 162 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 163 | * To change the currently loaded video without reloading the entire UIWebView, use the 164 | * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. 165 | * 166 | * @param videoId The YouTube video ID of the video to load in the player view. 167 | * @param playerVars An NSDictionary of player parameters. 168 | * @return YES if player has been configured correctly, NO otherwise. 169 | */ 170 | - (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars; 171 | 172 | /** 173 | * This method loads the player with the given playlist ID and player variables. Player variables 174 | * specify optional parameters for video playback. For instance, to play a YouTube 175 | * video inline, the following playerVars dictionary would be used: 176 | * 177 | * @code 178 | * @{ @"playsinline" : @1 }; 179 | * @endcode 180 | * 181 | * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), 182 | * both strings and integers are valid values. The full list of parameters is defined at: 183 | * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. 184 | * 185 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 186 | * To change the currently loaded video without reloading the entire UIWebView, use the 187 | * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: 188 | * family of methods. 189 | * 190 | * @param playlistId The YouTube playlist ID of the playlist to load in the player view. 191 | * @param playerVars An NSDictionary of player parameters. 192 | * @return YES if player has been configured correctly, NO otherwise. 193 | */ 194 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars; 195 | 196 | - (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams; 197 | 198 | #pragma mark - Player controls 199 | 200 | // These methods correspond to their JavaScript equivalents as documented here: 201 | // https://developers.google.com/youtube/iframe_api_reference#Playback_controls 202 | 203 | /** 204 | * Starts or resumes playback on the loaded video. Corresponds to this method from 205 | * the JavaScript API: 206 | * https://developers.google.com/youtube/iframe_api_reference#playVideo 207 | */ 208 | - (void)playVideo; 209 | 210 | /** 211 | * Pauses playback on a playing video. Corresponds to this method from 212 | * the JavaScript API: 213 | * https://developers.google.com/youtube/iframe_api_reference#pauseVideo 214 | */ 215 | - (void)pauseVideo; 216 | 217 | /** 218 | * Stops playback on a playing video. Corresponds to this method from 219 | * the JavaScript API: 220 | * https://developers.google.com/youtube/iframe_api_reference#stopVideo 221 | */ 222 | - (void)stopVideo; 223 | 224 | /** 225 | * Seek to a given time on a playing video. Corresponds to this method from 226 | * the JavaScript API: 227 | * https://developers.google.com/youtube/iframe_api_reference#seekTo 228 | * 229 | * @param seekToSeconds The time in seconds to seek to in the loaded video. 230 | * @param allowSeekAhead Whether to make a new request to the server if the time is 231 | * outside what is currently buffered. Recommended to set to YES. 232 | */ 233 | - (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead; 234 | 235 | /** 236 | * Clears the loaded video from the player. Corresponds to this method from 237 | * the JavaScript API: 238 | * https://developers.google.com/youtube/iframe_api_reference#clearVideo 239 | */ 240 | - (void)clearVideo; 241 | 242 | #pragma mark - Queuing videos 243 | 244 | // Queueing functions for videos. These methods correspond to their JavaScript 245 | // equivalents as documented here: 246 | // https://developers.google.com/youtube/iframe_api_reference#Queueing_Functions 247 | 248 | /** 249 | * Cues a given video by its video ID for playback starting at the given time and with the 250 | * suggested quality. Cueing loads a video, but does not start video playback. This method 251 | * corresponds with its JavaScript API equivalent as documented here: 252 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoById 253 | * 254 | * @param videoId A video ID to cue. 255 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 256 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 257 | */ 258 | - (void)cueVideoById:(NSString *)videoId 259 | startSeconds:(float)startSeconds 260 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 261 | 262 | /** 263 | * Cues a given video by its video ID for playback starting and ending at the given times 264 | * with the suggested quality. Cueing loads a video, but does not start video playback. This 265 | * method corresponds with its JavaScript API equivalent as documented here: 266 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoById 267 | * 268 | * @param videoId A video ID to cue. 269 | * @param startSeconds Time in seconds to start the video when playVideo() is called. 270 | * @param endSeconds Time in seconds to end the video after it begins playing. 271 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 272 | */ 273 | - (void)cueVideoById:(NSString *)videoId 274 | startSeconds:(float)startSeconds 275 | endSeconds:(float)endSeconds 276 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 277 | 278 | /** 279 | * Loads a given video by its video ID for playback starting at the given time and with the 280 | * suggested quality. Loading a video both loads it and begins playback. This method 281 | * corresponds with its JavaScript API equivalent as documented here: 282 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoById 283 | * 284 | * @param videoId A video ID to load and begin playing. 285 | * @param startSeconds Time in seconds to start the video when it has loaded. 286 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 287 | */ 288 | - (void)loadVideoById:(NSString *)videoId 289 | startSeconds:(float)startSeconds 290 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 291 | 292 | /** 293 | * Loads a given video by its video ID for playback starting and ending at the given times 294 | * with the suggested quality. Loading a video both loads it and begins playback. This method 295 | * corresponds with its JavaScript API equivalent as documented here: 296 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoById 297 | * 298 | * @param videoId A video ID to load and begin playing. 299 | * @param startSeconds Time in seconds to start the video when it has loaded. 300 | * @param endSeconds Time in seconds to end the video after it begins playing. 301 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 302 | */ 303 | - (void)loadVideoById:(NSString *)videoId 304 | startSeconds:(float)startSeconds 305 | endSeconds:(float)endSeconds 306 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 307 | 308 | /** 309 | * Cues a given video by its URL on YouTube.com for playback starting at the given time 310 | * and with the suggested quality. Cueing loads a video, but does not start video playback. 311 | * This method corresponds with its JavaScript API equivalent as documented here: 312 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl 313 | * 314 | * @param videoURL URL of a YouTube video to cue for playback. 315 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 316 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 317 | */ 318 | - (void)cueVideoByURL:(NSString *)videoURL 319 | startSeconds:(float)startSeconds 320 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 321 | 322 | /** 323 | * Cues a given video by its URL on YouTube.com for playback starting at the given time 324 | * and with the suggested quality. Cueing loads a video, but does not start video playback. 325 | * This method corresponds with its JavaScript API equivalent as documented here: 326 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl 327 | * 328 | * @param videoURL URL of a YouTube video to cue for playback. 329 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 330 | * @param endSeconds Time in seconds to end the video after it begins playing. 331 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 332 | */ 333 | - (void)cueVideoByURL:(NSString *)videoURL 334 | startSeconds:(float)startSeconds 335 | endSeconds:(float)endSeconds 336 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 337 | 338 | /** 339 | * Loads a given video by its video ID for playback starting at the given time 340 | * with the suggested quality. Loading a video both loads it and begins playback. This method 341 | * corresponds with its JavaScript API equivalent as documented here: 342 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl 343 | * 344 | * @param videoURL URL of a YouTube video to load and play. 345 | * @param startSeconds Time in seconds to start the video when it has loaded. 346 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 347 | */ 348 | - (void)loadVideoByURL:(NSString *)videoURL 349 | startSeconds:(float)startSeconds 350 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 351 | 352 | /** 353 | * Loads a given video by its video ID for playback starting and ending at the given times 354 | * with the suggested quality. Loading a video both loads it and begins playback. This method 355 | * corresponds with its JavaScript API equivalent as documented here: 356 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl 357 | * 358 | * @param videoURL URL of a YouTube video to load and play. 359 | * @param startSeconds Time in seconds to start the video when it has loaded. 360 | * @param endSeconds Time in seconds to end the video after it begins playing. 361 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 362 | */ 363 | - (void)loadVideoByURL:(NSString *)videoURL 364 | startSeconds:(float)startSeconds 365 | endSeconds:(float)endSeconds 366 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 367 | 368 | #pragma mark - Queuing functions for playlists 369 | 370 | // Queueing functions for playlists. These methods correspond to 371 | // the JavaScript methods defined here: 372 | // https://developers.google.com/youtube/js_api_reference#Playlist_Queueing_Functions 373 | 374 | /** 375 | * Cues a given playlist with the given ID. The |index| parameter specifies the 0-indexed 376 | * position of the first video to play, starting at the given time and with the 377 | * suggested quality. Cueing loads a playlist, but does not start video playback. This method 378 | * corresponds with its JavaScript API equivalent as documented here: 379 | * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist 380 | * 381 | * @param playlistId Playlist ID of a YouTube playlist to cue. 382 | * @param index A 0-indexed position specifying the first video to play. 383 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 384 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 385 | */ 386 | - (void)cuePlaylistByPlaylistId:(NSString *)playlistId 387 | index:(int)index 388 | startSeconds:(float)startSeconds 389 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 390 | 391 | /** 392 | * Cues a playlist of videos with the given video IDs. The |index| parameter specifies the 393 | * 0-indexed position of the first video to play, starting at the given time and with the 394 | * suggested quality. Cueing loads a playlist, but does not start video playback. This method 395 | * corresponds with its JavaScript API equivalent as documented here: 396 | * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist 397 | * 398 | * @param videoIds An NSArray of video IDs to compose the playlist of. 399 | * @param index A 0-indexed position specifying the first video to play. 400 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 401 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 402 | */ 403 | - (void)cuePlaylistByVideos:(NSArray *)videoIds 404 | index:(int)index 405 | startSeconds:(float)startSeconds 406 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 407 | 408 | /** 409 | * Loads a given playlist with the given ID. The |index| parameter specifies the 0-indexed 410 | * position of the first video to play, starting at the given time and with the 411 | * suggested quality. Loading a playlist starts video playback. This method 412 | * corresponds with its JavaScript API equivalent as documented here: 413 | * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist 414 | * 415 | * @param playlistId Playlist ID of a YouTube playlist to cue. 416 | * @param index A 0-indexed position specifying the first video to play. 417 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 418 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 419 | */ 420 | - (void)loadPlaylistByPlaylistId:(NSString *)playlistId 421 | index:(int)index 422 | startSeconds:(float)startSeconds 423 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 424 | 425 | /** 426 | * Loads a playlist of videos with the given video IDs. The |index| parameter specifies the 427 | * 0-indexed position of the first video to play, starting at the given time and with the 428 | * suggested quality. Loading a playlist starts video playback. This method 429 | * corresponds with its JavaScript API equivalent as documented here: 430 | * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist 431 | * 432 | * @param videoIds An NSArray of video IDs to compose the playlist of. 433 | * @param index A 0-indexed position specifying the first video to play. 434 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 435 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 436 | */ 437 | - (void)loadPlaylistByVideos:(NSArray *)videoIds 438 | index:(int)index 439 | startSeconds:(float)startSeconds 440 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 441 | 442 | #pragma mark - Playing a video in a playlist 443 | 444 | // These methods correspond to the JavaScript API as defined under the 445 | // "Playing a video in a playlist" section here: 446 | // https://developers.google.com/youtube/iframe_api_reference#Playback_status 447 | 448 | /** 449 | * Loads and plays the next video in the playlist. Corresponds to this method from 450 | * the JavaScript API: 451 | * https://developers.google.com/youtube/iframe_api_reference#nextVideo 452 | */ 453 | - (void)nextVideo; 454 | 455 | /** 456 | * Loads and plays the previous video in the playlist. Corresponds to this method from 457 | * the JavaScript API: 458 | * https://developers.google.com/youtube/iframe_api_reference#previousVideo 459 | */ 460 | - (void)previousVideo; 461 | 462 | /** 463 | * Loads and plays the video at the given 0-indexed position in the playlist. 464 | * Corresponds to this method from the JavaScript API: 465 | * https://developers.google.com/youtube/iframe_api_reference#playVideoAt 466 | * 467 | * @param index The 0-indexed position of the video in the playlist to load and play. 468 | */ 469 | - (void)playVideoAt:(int)index; 470 | 471 | #pragma mark - Setting the playback rate 472 | 473 | /** 474 | * Gets the playback rate. The default value is 1.0, which represents a video 475 | * playing at normal speed. Other values may include 0.25 or 0.5 for slower 476 | * speeds, and 1.5 or 2.0 for faster speeds. This method corresponds to the 477 | * JavaScript API defined here: 478 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate 479 | * 480 | * @return An integer value between 0 and 100 representing the current volume. 481 | */ 482 | - (float)playbackRate; 483 | 484 | /** 485 | * Sets the playback rate. The default value is 1.0, which represents a video 486 | * playing at normal speed. Other values may include 0.25 or 0.5 for slower 487 | * speeds, and 1.5 or 2.0 for faster speeds. To fetch a list of valid values for 488 | * this method, call YTPlayerView::getAvailablePlaybackRates. This method does not 489 | * guarantee that the playback rate will change. 490 | * This method corresponds to the JavaScript API defined here: 491 | * https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate 492 | * 493 | * @param suggestedRate A playback rate to suggest for the player. 494 | */ 495 | - (void)setPlaybackRate:(float)suggestedRate; 496 | 497 | /** 498 | * Gets a list of the valid playback rates, useful in conjunction with 499 | * YTPlayerView::setPlaybackRate. This method corresponds to the 500 | * JavaScript API defined here: 501 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate 502 | * 503 | * @return An NSArray containing available playback rates. nil if there is an error. 504 | */ 505 | - (NSArray *)availablePlaybackRates; 506 | 507 | #pragma mark - Setting playback behavior for playlists 508 | 509 | /** 510 | * Sets whether the player should loop back to the first video in the playlist 511 | * after it has finished playing the last video. This method corresponds to the 512 | * JavaScript API defined here: 513 | * https://developers.google.com/youtube/iframe_api_reference#loopPlaylist 514 | * 515 | * @param loop A boolean representing whether the player should loop. 516 | */ 517 | - (void)setLoop:(BOOL)loop; 518 | 519 | /** 520 | * Sets whether the player should shuffle through the playlist. This method 521 | * corresponds to the JavaScript API defined here: 522 | * https://developers.google.com/youtube/iframe_api_reference#shufflePlaylist 523 | * 524 | * @param shuffle A boolean representing whether the player should 525 | * shuffle through the playlist. 526 | */ 527 | - (void)setShuffle:(BOOL)shuffle; 528 | 529 | #pragma mark - Playback status 530 | // These methods correspond to the JavaScript methods defined here: 531 | // https://developers.google.com/youtube/js_api_reference#Playback_status 532 | 533 | /** 534 | * Returns a number between 0 and 1 that specifies the percentage of the video 535 | * that the player shows as buffered. This method corresponds to the 536 | * JavaScript API defined here: 537 | * https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction 538 | * 539 | * @return A float value between 0 and 1 representing the percentage of the video 540 | * already loaded. 541 | */ 542 | - (float)videoLoadedFraction; 543 | 544 | /** 545 | * Returns the state of the player. This method corresponds to the 546 | * JavaScript API defined here: 547 | * https://developers.google.com/youtube/iframe_api_reference#getPlayerState 548 | * 549 | * @return |YTPlayerState| representing the state of the player. 550 | */ 551 | - (YTPlayerState)playerState; 552 | 553 | /** 554 | * Returns the elapsed time in seconds since the video started playing. This 555 | * method corresponds to the JavaScript API defined here: 556 | * https://developers.google.com/youtube/iframe_api_reference#getCurrentTime 557 | * 558 | * @return Time in seconds since the video started playing. 559 | */ 560 | - (float)currentTime; 561 | 562 | #pragma mark - Playback quality 563 | 564 | // Playback quality. These methods correspond to the JavaScript 565 | // methods defined here: 566 | // https://developers.google.com/youtube/js_api_reference#Playback_quality 567 | 568 | /** 569 | * Returns the playback quality. This method corresponds to the 570 | * JavaScript API defined here: 571 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality 572 | * 573 | * @return YTPlaybackQuality representing the current playback quality. 574 | */ 575 | - (YTPlaybackQuality)playbackQuality; 576 | 577 | /** 578 | * Suggests playback quality for the video. It is recommended to leave this setting to 579 | * |default|. This method corresponds to the JavaScript API defined here: 580 | * https://developers.google.com/youtube/iframe_api_reference#setPlaybackQuality 581 | * 582 | * @param quality YTPlaybackQuality value to suggest for the player. 583 | */ 584 | - (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality; 585 | 586 | /** 587 | * Gets a list of the valid playback quality values, useful in conjunction with 588 | * YTPlayerView::setPlaybackQuality. This method corresponds to the 589 | * JavaScript API defined here: 590 | * https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels 591 | * 592 | * @return An NSArray containing available playback quality levels. 593 | */ 594 | - (NSArray *)availableQualityLevels; 595 | 596 | #pragma mark - Retrieving video information 597 | 598 | // Retrieving video information. These methods correspond to the JavaScript 599 | // methods defined here: 600 | // https://developers.google.com/youtube/js_api_reference#Retrieving_video_information 601 | 602 | /** 603 | * Returns the duration in seconds since the video of the video. This 604 | * method corresponds to the JavaScript API defined here: 605 | * https://developers.google.com/youtube/iframe_api_reference#getDuration 606 | * 607 | * @return Length of the video in seconds. 608 | */ 609 | - (NSTimeInterval)duration; 610 | 611 | /** 612 | * Returns the YouTube.com URL for the video. This method corresponds 613 | * to the JavaScript API defined here: 614 | * https://developers.google.com/youtube/iframe_api_reference#getVideoUrl 615 | * 616 | * @return The YouTube.com URL for the video. 617 | */ 618 | - (NSURL *)videoUrl; 619 | 620 | /** 621 | * Returns the embed code for the current video. This method corresponds 622 | * to the JavaScript API defined here: 623 | * https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode 624 | * 625 | * @return The embed code for the current video. 626 | */ 627 | - (NSString *)videoEmbedCode; 628 | 629 | #pragma mark - Retrieving playlist information 630 | 631 | // Retrieving playlist information. These methods correspond to the 632 | // JavaScript defined here: 633 | // https://developers.google.com/youtube/js_api_reference#Retrieving_playlist_information 634 | 635 | /** 636 | * Returns an ordered array of video IDs in the playlist. This method corresponds 637 | * to the JavaScript API defined here: 638 | * https://developers.google.com/youtube/iframe_api_reference#getPlaylist 639 | * 640 | * @return An NSArray containing all the video IDs in the current playlist. |nil| on error. 641 | */ 642 | - (NSArray *)playlist; 643 | 644 | /** 645 | * Returns the 0-based index of the currently playing item in the playlist. 646 | * This method corresponds to the JavaScript API defined here: 647 | * https://developers.google.com/youtube/iframe_api_reference#getPlaylistIndex 648 | * 649 | * @return The 0-based index of the currently playing item in the playlist. 650 | */ 651 | - (int)playlistIndex; 652 | 653 | - (void)removeWebView; 654 | 655 | @end 656 | -------------------------------------------------------------------------------- /Assets/YTPlayerView.m: -------------------------------------------------------------------------------- 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 | 15 | #import "YTPlayerView.h" 16 | 17 | // These are instances of NSString because we get them from parsing a URL. It would be silly to 18 | // convert these into an integer just to have to convert the URL query string value into an integer 19 | // as well for the sake of doing a value comparison. A full list of response error codes can be 20 | // found here: 21 | // https://developers.google.com/youtube/iframe_api_reference 22 | NSString static *const kYTPlayerStateUnstartedCode = @"-1"; 23 | NSString static *const kYTPlayerStateEndedCode = @"0"; 24 | NSString static *const kYTPlayerStatePlayingCode = @"1"; 25 | NSString static *const kYTPlayerStatePausedCode = @"2"; 26 | NSString static *const kYTPlayerStateBufferingCode = @"3"; 27 | NSString static *const kYTPlayerStateCuedCode = @"5"; 28 | NSString static *const kYTPlayerStateUnknownCode = @"unknown"; 29 | 30 | // Constants representing playback quality. 31 | NSString static *const kYTPlaybackQualitySmallQuality = @"small"; 32 | NSString static *const kYTPlaybackQualityMediumQuality = @"medium"; 33 | NSString static *const kYTPlaybackQualityLargeQuality = @"large"; 34 | NSString static *const kYTPlaybackQualityHD720Quality = @"hd720"; 35 | NSString static *const kYTPlaybackQualityHD1080Quality = @"hd1080"; 36 | NSString static *const kYTPlaybackQualityHighResQuality = @"highres"; 37 | NSString static *const kYTPlaybackQualityAutoQuality = @"auto"; 38 | NSString static *const kYTPlaybackQualityDefaultQuality = @"default"; 39 | NSString static *const kYTPlaybackQualityUnknownQuality = @"unknown"; 40 | 41 | // Constants representing YouTube player errors. 42 | NSString static *const kYTPlayerErrorInvalidParamErrorCode = @"2"; 43 | NSString static *const kYTPlayerErrorHTML5ErrorCode = @"5"; 44 | NSString static *const kYTPlayerErrorVideoNotFoundErrorCode = @"100"; 45 | NSString static *const kYTPlayerErrorNotEmbeddableErrorCode = @"101"; 46 | NSString static *const kYTPlayerErrorCannotFindVideoErrorCode = @"105"; 47 | NSString static *const kYTPlayerErrorSameAsNotEmbeddableErrorCode = @"150"; 48 | 49 | // Constants representing player callbacks. 50 | NSString static *const kYTPlayerCallbackOnReady = @"onReady"; 51 | NSString static *const kYTPlayerCallbackOnStateChange = @"onStateChange"; 52 | NSString static *const kYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange"; 53 | NSString static *const kYTPlayerCallbackOnError = @"onError"; 54 | NSString static *const kYTPlayerCallbackOnPlayTime = @"onPlayTime"; 55 | 56 | NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIReady = @"onYouTubeIframeAPIReady"; 57 | 58 | NSString static *const kYTPlayerEmbedUrlRegexPattern = @"^http(s)://(www.)youtube.com/embed/(.*)$"; 59 | NSString static *const kYTPlayerAdUrlRegexPattern = @"^http(s)://pubads.g.doubleclick.net/pagead/conversion/"; 60 | NSString static *const kYTPlayerOAuthRegexPattern = @"^http(s)://accounts.google.com/o/oauth2/(.*)$"; 61 | NSString static *const kYTPlayerStaticProxyRegexPattern = @"^https://content.googleapis.com/static/proxy.html(.*)$"; 62 | 63 | @interface YTPlayerView() 64 | 65 | @property(nonatomic, strong) NSURL *originURL; 66 | 67 | @end 68 | 69 | @implementation YTPlayerView 70 | 71 | - (BOOL)loadWithVideoId:(NSString *)videoId { 72 | return [self loadWithVideoId:videoId playerVars:nil]; 73 | } 74 | 75 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId { 76 | return [self loadWithPlaylistId:playlistId playerVars:nil]; 77 | } 78 | 79 | - (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars { 80 | if (!playerVars) { 81 | playerVars = @{}; 82 | } 83 | NSDictionary *playerParams = @{ @"videoId" : videoId, @"playerVars" : playerVars }; 84 | return [self loadWithPlayerParams:playerParams]; 85 | } 86 | 87 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars { 88 | 89 | // Mutable copy because we may have been passed an immutable config dictionary. 90 | NSMutableDictionary *tempPlayerVars = [[NSMutableDictionary alloc] init]; 91 | [tempPlayerVars setValue:@"playlist" forKey:@"listType"]; 92 | [tempPlayerVars setValue:playlistId forKey:@"list"]; 93 | [tempPlayerVars addEntriesFromDictionary:playerVars]; // No-op if playerVars is null 94 | 95 | NSDictionary *playerParams = @{ @"playerVars" : tempPlayerVars }; 96 | return [self loadWithPlayerParams:playerParams]; 97 | } 98 | 99 | #pragma mark - Player methods 100 | 101 | - (void)playVideo { 102 | [self stringFromEvaluatingJavaScript:@"player.playVideo();"]; 103 | } 104 | 105 | - (void)pauseVideo { 106 | [self notifyDelegateOfYouTubeCallbackUrl:[NSURL URLWithString:[NSString stringWithFormat:@"ytplayer://onStateChange?data=%@", kYTPlayerStatePausedCode]]]; 107 | [self stringFromEvaluatingJavaScript:@"player.pauseVideo();"]; 108 | } 109 | 110 | - (void)stopVideo { 111 | [self stringFromEvaluatingJavaScript:@"player.stopVideo();"]; 112 | } 113 | 114 | - (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead { 115 | NSNumber *secondsValue = [NSNumber numberWithFloat:seekToSeconds]; 116 | NSString *allowSeekAheadValue = [self stringForJSBoolean:allowSeekAhead]; 117 | NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, %@);", secondsValue, allowSeekAheadValue]; 118 | [self stringFromEvaluatingJavaScript:command]; 119 | } 120 | 121 | - (void)clearVideo { 122 | [self stringFromEvaluatingJavaScript:@"player.clearVideo();"]; 123 | } 124 | 125 | #pragma mark - Cueing methods 126 | 127 | - (void)cueVideoById:(NSString *)videoId 128 | startSeconds:(float)startSeconds 129 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 130 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 131 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 132 | NSString *command = [NSString stringWithFormat:@"player.cueVideoById('%@', %@, '%@');", 133 | videoId, startSecondsValue, qualityValue]; 134 | [self stringFromEvaluatingJavaScript:command]; 135 | } 136 | 137 | - (void)cueVideoById:(NSString *)videoId 138 | startSeconds:(float)startSeconds 139 | endSeconds:(float)endSeconds 140 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 141 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 142 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 143 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 144 | NSString *command = [NSString stringWithFormat:@"player.cueVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});", videoId, startSecondsValue, endSecondsValue, qualityValue]; 145 | [self stringFromEvaluatingJavaScript:command]; 146 | } 147 | 148 | - (void)loadVideoById:(NSString *)videoId 149 | startSeconds:(float)startSeconds 150 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 151 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 152 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 153 | NSString *command = [NSString stringWithFormat:@"player.loadVideoById('%@', %@, '%@');", 154 | videoId, startSecondsValue, qualityValue]; 155 | [self stringFromEvaluatingJavaScript:command]; 156 | } 157 | 158 | - (void)loadVideoById:(NSString *)videoId 159 | startSeconds:(float)startSeconds 160 | endSeconds:(float)endSeconds 161 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 162 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 163 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 164 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 165 | NSString *command = [NSString stringWithFormat:@"player.loadVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});",videoId, startSecondsValue, endSecondsValue, qualityValue]; 166 | [self stringFromEvaluatingJavaScript:command]; 167 | } 168 | 169 | - (void)cueVideoByURL:(NSString *)videoURL 170 | startSeconds:(float)startSeconds 171 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 172 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 173 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 174 | NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, '%@');", 175 | videoURL, startSecondsValue, qualityValue]; 176 | [self stringFromEvaluatingJavaScript:command]; 177 | } 178 | 179 | - (void)cueVideoByURL:(NSString *)videoURL 180 | startSeconds:(float)startSeconds 181 | endSeconds:(float)endSeconds 182 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 183 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 184 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 185 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 186 | NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, %@, '%@');", 187 | videoURL, startSecondsValue, endSecondsValue, qualityValue]; 188 | [self stringFromEvaluatingJavaScript:command]; 189 | } 190 | 191 | - (void)loadVideoByURL:(NSString *)videoURL 192 | startSeconds:(float)startSeconds 193 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 194 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 195 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 196 | NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, '%@');", 197 | videoURL, startSecondsValue, qualityValue]; 198 | [self stringFromEvaluatingJavaScript:command]; 199 | } 200 | 201 | - (void)loadVideoByURL:(NSString *)videoURL 202 | startSeconds:(float)startSeconds 203 | endSeconds:(float)endSeconds 204 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 205 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 206 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 207 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 208 | NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, %@, '%@');", 209 | videoURL, startSecondsValue, endSecondsValue, qualityValue]; 210 | [self stringFromEvaluatingJavaScript:command]; 211 | } 212 | 213 | #pragma mark - Cueing methods for lists 214 | 215 | - (void)cuePlaylistByPlaylistId:(NSString *)playlistId 216 | index:(int)index 217 | startSeconds:(float)startSeconds 218 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 219 | NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; 220 | [self cuePlaylist:playlistIdString 221 | index:index 222 | startSeconds:startSeconds 223 | suggestedQuality:suggestedQuality]; 224 | } 225 | 226 | - (void)cuePlaylistByVideos:(NSArray *)videoIds 227 | index:(int)index 228 | startSeconds:(float)startSeconds 229 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 230 | [self cuePlaylist:[self stringFromVideoIdArray:videoIds] 231 | index:index 232 | startSeconds:startSeconds 233 | suggestedQuality:suggestedQuality]; 234 | } 235 | 236 | - (void)loadPlaylistByPlaylistId:(NSString *)playlistId 237 | index:(int)index 238 | startSeconds:(float)startSeconds 239 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 240 | NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; 241 | [self loadPlaylist:playlistIdString 242 | index:index 243 | startSeconds:startSeconds 244 | suggestedQuality:suggestedQuality]; 245 | } 246 | 247 | - (void)loadPlaylistByVideos:(NSArray *)videoIds 248 | index:(int)index 249 | startSeconds:(float)startSeconds 250 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 251 | [self loadPlaylist:[self stringFromVideoIdArray:videoIds] 252 | index:index 253 | startSeconds:startSeconds 254 | suggestedQuality:suggestedQuality]; 255 | } 256 | 257 | #pragma mark - Setting the playback rate 258 | 259 | - (float)playbackRate { 260 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackRate();"]; 261 | return [returnValue floatValue]; 262 | } 263 | 264 | - (void)setPlaybackRate:(float)suggestedRate { 265 | NSString *command = [NSString stringWithFormat:@"player.setPlaybackRate(%f);", suggestedRate]; 266 | [self stringFromEvaluatingJavaScript:command]; 267 | } 268 | 269 | - (NSArray *)availablePlaybackRates { 270 | NSString *returnValue = 271 | [self stringFromEvaluatingJavaScript:@"player.getAvailablePlaybackRates();"]; 272 | 273 | NSData *playbackRateData = [returnValue dataUsingEncoding:NSUTF8StringEncoding]; 274 | NSError *jsonDeserializationError; 275 | NSArray *playbackRates = [NSJSONSerialization JSONObjectWithData:playbackRateData 276 | options:kNilOptions 277 | error:&jsonDeserializationError]; 278 | if (jsonDeserializationError) { 279 | return nil; 280 | } 281 | 282 | return playbackRates; 283 | } 284 | 285 | #pragma mark - Setting playback behavior for playlists 286 | 287 | - (void)setLoop:(BOOL)loop { 288 | NSString *loopPlayListValue = [self stringForJSBoolean:loop]; 289 | NSString *command = [NSString stringWithFormat:@"player.setLoop(%@);", loopPlayListValue]; 290 | [self stringFromEvaluatingJavaScript:command]; 291 | } 292 | 293 | - (void)setShuffle:(BOOL)shuffle { 294 | NSString *shufflePlayListValue = [self stringForJSBoolean:shuffle]; 295 | NSString *command = [NSString stringWithFormat:@"player.setShuffle(%@);", shufflePlayListValue]; 296 | [self stringFromEvaluatingJavaScript:command]; 297 | } 298 | 299 | #pragma mark - Playback status 300 | 301 | - (float)videoLoadedFraction { 302 | return [[self stringFromEvaluatingJavaScript:@"player.getVideoLoadedFraction();"] floatValue]; 303 | } 304 | 305 | - (YTPlayerState)playerState { 306 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlayerState();"]; 307 | return [YTPlayerView playerStateForString:returnValue]; 308 | } 309 | 310 | - (float)currentTime { 311 | return [[self stringFromEvaluatingJavaScript:@"player.getCurrentTime();"] floatValue]; 312 | } 313 | 314 | // Playback quality 315 | - (YTPlaybackQuality)playbackQuality { 316 | NSString *qualityValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackQuality();"]; 317 | return [YTPlayerView playbackQualityForString:qualityValue]; 318 | } 319 | 320 | - (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality { 321 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 322 | NSString *command = [NSString stringWithFormat:@"player.setPlaybackQuality('%@');", qualityValue]; 323 | [self stringFromEvaluatingJavaScript:command]; 324 | } 325 | 326 | #pragma mark - Video information methods 327 | 328 | - (NSTimeInterval)duration { 329 | return [[self stringFromEvaluatingJavaScript:@"player.getDuration();"] doubleValue]; 330 | } 331 | 332 | - (NSURL *)videoUrl { 333 | return [NSURL URLWithString:[self stringFromEvaluatingJavaScript:@"player.getVideoUrl();"]]; 334 | } 335 | 336 | - (NSString *)videoEmbedCode { 337 | return [self stringFromEvaluatingJavaScript:@"player.getVideoEmbedCode();"]; 338 | } 339 | 340 | #pragma mark - Playlist methods 341 | 342 | - (NSArray *)playlist { 343 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylist();"]; 344 | 345 | NSData *playlistData = [returnValue dataUsingEncoding:NSUTF8StringEncoding]; 346 | NSError *jsonDeserializationError; 347 | NSArray *videoIds = [NSJSONSerialization JSONObjectWithData:playlistData 348 | options:kNilOptions 349 | error:&jsonDeserializationError]; 350 | if (jsonDeserializationError) { 351 | return nil; 352 | } 353 | 354 | return videoIds; 355 | } 356 | 357 | - (int)playlistIndex { 358 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylistIndex();"]; 359 | return [returnValue intValue]; 360 | } 361 | 362 | #pragma mark - Playing a video in a playlist 363 | 364 | - (void)nextVideo { 365 | [self stringFromEvaluatingJavaScript:@"player.nextVideo();"]; 366 | } 367 | 368 | - (void)previousVideo { 369 | [self stringFromEvaluatingJavaScript:@"player.previousVideo();"]; 370 | } 371 | 372 | - (void)playVideoAt:(int)index { 373 | NSString *command = 374 | [NSString stringWithFormat:@"player.playVideoAt(%@);", [NSNumber numberWithInt:index]]; 375 | [self stringFromEvaluatingJavaScript:command]; 376 | } 377 | 378 | #pragma mark - Helper methods 379 | 380 | - (NSArray *)availableQualityLevels { 381 | NSString *returnValue = 382 | [self stringFromEvaluatingJavaScript:@"player.getAvailableQualityLevels().toString();"]; 383 | if(!returnValue) return nil; 384 | 385 | NSArray *rawQualityValues = [returnValue componentsSeparatedByString:@","]; 386 | NSMutableArray *levels = [[NSMutableArray alloc] init]; 387 | for (NSString *rawQualityValue in rawQualityValues) { 388 | YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:rawQualityValue]; 389 | [levels addObject:[NSNumber numberWithInt:quality]]; 390 | } 391 | return levels; 392 | } 393 | 394 | - (BOOL)webView:(UIWebView *)webView 395 | shouldStartLoadWithRequest:(NSURLRequest *)request 396 | navigationType:(UIWebViewNavigationType)navigationType { 397 | if ([request.URL.host isEqual: self.originURL.host]) { 398 | return YES; 399 | } else if ([request.URL.scheme isEqual:@"ytplayer"]) { 400 | [self notifyDelegateOfYouTubeCallbackUrl:request.URL]; 401 | return NO; 402 | } else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) { 403 | return [self handleHttpNavigationToUrl:request.URL]; 404 | } 405 | return YES; 406 | } 407 | 408 | /** 409 | * Convert a quality value from NSString to the typed enum value. 410 | * 411 | * @param qualityString A string representing playback quality. Ex: "small", "medium", "hd1080". 412 | * @return An enum value representing the playback quality. 413 | */ 414 | + (YTPlaybackQuality)playbackQualityForString:(NSString *)qualityString { 415 | YTPlaybackQuality quality = kYTPlaybackQualityUnknown; 416 | 417 | if ([qualityString isEqualToString:kYTPlaybackQualitySmallQuality]) { 418 | quality = kYTPlaybackQualitySmall; 419 | } else if ([qualityString isEqualToString:kYTPlaybackQualityMediumQuality]) { 420 | quality = kYTPlaybackQualityMedium; 421 | } else if ([qualityString isEqualToString:kYTPlaybackQualityLargeQuality]) { 422 | quality = kYTPlaybackQualityLarge; 423 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHD720Quality]) { 424 | quality = kYTPlaybackQualityHD720; 425 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHD1080Quality]) { 426 | quality = kYTPlaybackQualityHD1080; 427 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHighResQuality]) { 428 | quality = kYTPlaybackQualityHighRes; 429 | } else if ([qualityString isEqualToString:kYTPlaybackQualityAutoQuality]) { 430 | quality = kYTPlaybackQualityAuto; 431 | } 432 | 433 | return quality; 434 | } 435 | 436 | /** 437 | * Convert a |YTPlaybackQuality| value from the typed value to NSString. 438 | * 439 | * @param quality A |YTPlaybackQuality| parameter. 440 | * @return An |NSString| value to be used in the JavaScript bridge. 441 | */ 442 | + (NSString *)stringForPlaybackQuality:(YTPlaybackQuality)quality { 443 | switch (quality) { 444 | case kYTPlaybackQualitySmall: 445 | return kYTPlaybackQualitySmallQuality; 446 | case kYTPlaybackQualityMedium: 447 | return kYTPlaybackQualityMediumQuality; 448 | case kYTPlaybackQualityLarge: 449 | return kYTPlaybackQualityLargeQuality; 450 | case kYTPlaybackQualityHD720: 451 | return kYTPlaybackQualityHD720Quality; 452 | case kYTPlaybackQualityHD1080: 453 | return kYTPlaybackQualityHD1080Quality; 454 | case kYTPlaybackQualityHighRes: 455 | return kYTPlaybackQualityHighResQuality; 456 | case kYTPlaybackQualityAuto: 457 | return kYTPlaybackQualityAutoQuality; 458 | default: 459 | return kYTPlaybackQualityUnknownQuality; 460 | } 461 | } 462 | 463 | /** 464 | * Convert a state value from NSString to the typed enum value. 465 | * 466 | * @param stateString A string representing player state. Ex: "-1", "0", "1". 467 | * @return An enum value representing the player state. 468 | */ 469 | + (YTPlayerState)playerStateForString:(NSString *)stateString { 470 | YTPlayerState state = kYTPlayerStateUnknown; 471 | if ([stateString isEqualToString:kYTPlayerStateUnstartedCode]) { 472 | state = kYTPlayerStateUnstarted; 473 | } else if ([stateString isEqualToString:kYTPlayerStateEndedCode]) { 474 | state = kYTPlayerStateEnded; 475 | } else if ([stateString isEqualToString:kYTPlayerStatePlayingCode]) { 476 | state = kYTPlayerStatePlaying; 477 | } else if ([stateString isEqualToString:kYTPlayerStatePausedCode]) { 478 | state = kYTPlayerStatePaused; 479 | } else if ([stateString isEqualToString:kYTPlayerStateBufferingCode]) { 480 | state = kYTPlayerStateBuffering; 481 | } else if ([stateString isEqualToString:kYTPlayerStateCuedCode]) { 482 | state = kYTPlayerStateQueued; 483 | } 484 | return state; 485 | } 486 | 487 | /** 488 | * Convert a state value from the typed value to NSString. 489 | * 490 | * @param quality A |YTPlayerState| parameter. 491 | * @return A string value to be used in the JavaScript bridge. 492 | */ 493 | + (NSString *)stringForPlayerState:(YTPlayerState)state { 494 | switch (state) { 495 | case kYTPlayerStateUnstarted: 496 | return kYTPlayerStateUnstartedCode; 497 | case kYTPlayerStateEnded: 498 | return kYTPlayerStateEndedCode; 499 | case kYTPlayerStatePlaying: 500 | return kYTPlayerStatePlayingCode; 501 | case kYTPlayerStatePaused: 502 | return kYTPlayerStatePausedCode; 503 | case kYTPlayerStateBuffering: 504 | return kYTPlayerStateBufferingCode; 505 | case kYTPlayerStateQueued: 506 | return kYTPlayerStateCuedCode; 507 | default: 508 | return kYTPlayerStateUnknownCode; 509 | } 510 | } 511 | 512 | #pragma mark - Private methods 513 | 514 | /** 515 | * Private method to handle "navigation" to a callback URL of the format 516 | * ytplayer://action?data=someData 517 | * This is how the UIWebView communicates with the containing Objective-C code. 518 | * Side effects of this method are that it calls methods on this class's delegate. 519 | * 520 | * @param url A URL of the format ytplayer://action?data=value. 521 | */ 522 | - (void)notifyDelegateOfYouTubeCallbackUrl: (NSURL *) url { 523 | NSString *action = url.host; 524 | 525 | // We know the query can only be of the format ytplayer://action?data=SOMEVALUE, 526 | // so we parse out the value. 527 | NSString *query = url.query; 528 | NSString *data; 529 | if (query) { 530 | data = [query componentsSeparatedByString:@"="][1]; 531 | } 532 | 533 | if ([action isEqual:kYTPlayerCallbackOnReady]) { 534 | if ([self.delegate respondsToSelector:@selector(playerViewDidBecomeReady:)]) { 535 | [self.delegate playerViewDidBecomeReady:self]; 536 | } 537 | } else if ([action isEqual:kYTPlayerCallbackOnStateChange]) { 538 | if ([self.delegate respondsToSelector:@selector(playerView:didChangeToState:)]) { 539 | YTPlayerState state = kYTPlayerStateUnknown; 540 | 541 | if ([data isEqual:kYTPlayerStateEndedCode]) { 542 | state = kYTPlayerStateEnded; 543 | } else if ([data isEqual:kYTPlayerStatePlayingCode]) { 544 | state = kYTPlayerStatePlaying; 545 | } else if ([data isEqual:kYTPlayerStatePausedCode]) { 546 | state = kYTPlayerStatePaused; 547 | } else if ([data isEqual:kYTPlayerStateBufferingCode]) { 548 | state = kYTPlayerStateBuffering; 549 | } else if ([data isEqual:kYTPlayerStateCuedCode]) { 550 | state = kYTPlayerStateQueued; 551 | } else if ([data isEqual:kYTPlayerStateUnstartedCode]) { 552 | state = kYTPlayerStateUnstarted; 553 | } 554 | 555 | [self.delegate playerView:self didChangeToState:state]; 556 | } 557 | } else if ([action isEqual:kYTPlayerCallbackOnPlaybackQualityChange]) { 558 | if ([self.delegate respondsToSelector:@selector(playerView:didChangeToQuality:)]) { 559 | YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:data]; 560 | [self.delegate playerView:self didChangeToQuality:quality]; 561 | } 562 | } else if ([action isEqual:kYTPlayerCallbackOnError]) { 563 | if ([self.delegate respondsToSelector:@selector(playerView:receivedError:)]) { 564 | YTPlayerError error = kYTPlayerErrorUnknown; 565 | 566 | if ([data isEqual:kYTPlayerErrorInvalidParamErrorCode]) { 567 | error = kYTPlayerErrorInvalidParam; 568 | } else if ([data isEqual:kYTPlayerErrorHTML5ErrorCode]) { 569 | error = kYTPlayerErrorHTML5Error; 570 | } else if ([data isEqual:kYTPlayerErrorNotEmbeddableErrorCode] || 571 | [data isEqual:kYTPlayerErrorSameAsNotEmbeddableErrorCode]) { 572 | error = kYTPlayerErrorNotEmbeddable; 573 | } else if ([data isEqual:kYTPlayerErrorVideoNotFoundErrorCode] || 574 | [data isEqual:kYTPlayerErrorCannotFindVideoErrorCode]) { 575 | error = kYTPlayerErrorVideoNotFound; 576 | } 577 | 578 | [self.delegate playerView:self receivedError:error]; 579 | } 580 | } else if ([action isEqualToString:kYTPlayerCallbackOnPlayTime]) { 581 | if ([self.delegate respondsToSelector:@selector(playerView:didPlayTime:)]) { 582 | float time = [data floatValue]; 583 | [self.delegate playerView:self didPlayTime:time]; 584 | } 585 | 586 | } 587 | } 588 | 589 | - (BOOL)handleHttpNavigationToUrl:(NSURL *) url { 590 | // Usually this means the user has clicked on the YouTube logo or an error message in the 591 | // player. Most URLs should open in the browser. The only http(s) URL that should open in this 592 | // UIWebView is the URL for the embed, which is of the format: 593 | // http(s)://www.youtube.com/embed/[VIDEO ID]?[PARAMETERS] 594 | NSError *error = NULL; 595 | NSRegularExpression *ytRegex = 596 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerEmbedUrlRegexPattern 597 | options:NSRegularExpressionCaseInsensitive 598 | error:&error]; 599 | NSTextCheckingResult *ytMatch = 600 | [ytRegex firstMatchInString:url.absoluteString 601 | options:0 602 | range:NSMakeRange(0, [url.absoluteString length])]; 603 | 604 | NSRegularExpression *adRegex = 605 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerAdUrlRegexPattern 606 | options:NSRegularExpressionCaseInsensitive 607 | error:&error]; 608 | NSTextCheckingResult *adMatch = 609 | [adRegex firstMatchInString:url.absoluteString 610 | options:0 611 | range:NSMakeRange(0, [url.absoluteString length])]; 612 | 613 | NSRegularExpression *oauthRegex = 614 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerOAuthRegexPattern 615 | options:NSRegularExpressionCaseInsensitive 616 | error:&error]; 617 | NSTextCheckingResult *oauthMatch = 618 | [oauthRegex firstMatchInString:url.absoluteString 619 | options:0 620 | range:NSMakeRange(0, [url.absoluteString length])]; 621 | 622 | NSRegularExpression *staticProxyRegex = 623 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerStaticProxyRegexPattern 624 | options:NSRegularExpressionCaseInsensitive 625 | error:&error]; 626 | NSTextCheckingResult *staticProxyMatch = 627 | [staticProxyRegex firstMatchInString:url.absoluteString 628 | options:0 629 | range:NSMakeRange(0, [url.absoluteString length])]; 630 | 631 | if (ytMatch || adMatch || oauthMatch || staticProxyMatch) { 632 | return YES; 633 | } else { 634 | [[UIApplication sharedApplication] openURL:url]; 635 | return NO; 636 | } 637 | } 638 | 639 | 640 | /** 641 | * Private helper method to load an iframe player with the given player parameters. 642 | * 643 | * @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters 644 | * to instantiate the HTML5 player with. This differs depending on 645 | * whether a single video or playlist is being loaded. 646 | * @return YES if successful, NO if not. 647 | */ 648 | - (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams { 649 | NSDictionary *playerCallbacks = @{ 650 | @"onReady" : @"onReady", 651 | @"onStateChange" : @"onStateChange", 652 | @"onPlaybackQualityChange" : @"onPlaybackQualityChange", 653 | @"onError" : @"onPlayerError" 654 | }; 655 | NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init]; 656 | [playerParams addEntriesFromDictionary:additionalPlayerParams]; 657 | if (![playerParams objectForKey:@"height"]) { 658 | [playerParams setValue:@"100%" forKey:@"height"]; 659 | } 660 | if (![playerParams objectForKey:@"width"]) { 661 | [playerParams setValue:@"100%" forKey:@"width"]; 662 | } 663 | 664 | [playerParams setValue:playerCallbacks forKey:@"events"]; 665 | 666 | if ([playerParams objectForKey:@"playerVars"]) { 667 | NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init]; 668 | [playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]]; 669 | 670 | if (![playerVars objectForKey:@"origin"]) { 671 | self.originURL = [NSURL URLWithString:@"about:blank"]; 672 | } else { 673 | self.originURL = [NSURL URLWithString: [playerVars objectForKey:@"origin"]]; 674 | } 675 | } else { 676 | // This must not be empty so we can render a '{}' in the output JSON 677 | [playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"]; 678 | } 679 | 680 | // Remove the existing webView to reset any state 681 | [self.webView removeFromSuperview]; 682 | _webView = [self createNewWebView]; 683 | [self addSubview:self.webView]; 684 | 685 | NSError *error = nil; 686 | NSString *path = [[NSBundle mainBundle] pathForResource:@"YTPlayerView-iframe-player" 687 | ofType:@"html" 688 | inDirectory:@"Assets"]; 689 | NSString *embedHTMLTemplate = 690 | [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; 691 | 692 | if (error) { 693 | NSLog(@"Received error rendering template: %@", error); 694 | return NO; 695 | } 696 | 697 | // Render the playerVars as a JSON dictionary. 698 | NSError *jsonRenderingError = nil; 699 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams 700 | options:NSJSONWritingPrettyPrinted 701 | error:&jsonRenderingError]; 702 | if (jsonRenderingError) { 703 | NSLog(@"Attempted configuration of player with invalid playerVars: %@ \tError: %@", 704 | playerParams, 705 | jsonRenderingError); 706 | return NO; 707 | } 708 | 709 | NSString *playerVarsJsonString = 710 | [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 711 | 712 | NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString]; 713 | [self.webView loadHTMLString:embedHTML baseURL: self.originURL]; 714 | [self.webView setDelegate:self]; 715 | self.webView.allowsInlineMediaPlayback = YES; 716 | self.webView.mediaPlaybackRequiresUserAction = NO; 717 | return YES; 718 | } 719 | 720 | /** 721 | * Private method for cueing both cases of playlist ID and array of video IDs. Cueing 722 | * a playlist does not start playback. 723 | * 724 | * @param cueingString A JavaScript string representing an array, playlist ID or list of 725 | * video IDs to play with the playlist player. 726 | * @param index 0-index position of video to start playback on. 727 | * @param startSeconds Seconds after start of video to begin playback. 728 | * @param suggestedQuality Suggested YTPlaybackQuality to play the videos. 729 | * @return The result of cueing the playlist. 730 | */ 731 | - (void)cuePlaylist:(NSString *)cueingString 732 | index:(int)index 733 | startSeconds:(float)startSeconds 734 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 735 | NSNumber *indexValue = [NSNumber numberWithInt:index]; 736 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 737 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 738 | NSString *command = [NSString stringWithFormat:@"player.cuePlaylist(%@, %@, %@, '%@');", 739 | cueingString, indexValue, startSecondsValue, qualityValue]; 740 | [self stringFromEvaluatingJavaScript:command]; 741 | } 742 | 743 | /** 744 | * Private method for loading both cases of playlist ID and array of video IDs. Loading 745 | * a playlist automatically starts playback. 746 | * 747 | * @param cueingString A JavaScript string representing an array, playlist ID or list of 748 | * video IDs to play with the playlist player. 749 | * @param index 0-index position of video to start playback on. 750 | * @param startSeconds Seconds after start of video to begin playback. 751 | * @param suggestedQuality Suggested YTPlaybackQuality to play the videos. 752 | * @return The result of cueing the playlist. 753 | */ 754 | - (void)loadPlaylist:(NSString *)cueingString 755 | index:(int)index 756 | startSeconds:(float)startSeconds 757 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 758 | NSNumber *indexValue = [NSNumber numberWithInt:index]; 759 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 760 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 761 | NSString *command = [NSString stringWithFormat:@"player.loadPlaylist(%@, %@, %@, '%@');", 762 | cueingString, indexValue, startSecondsValue, qualityValue]; 763 | [self stringFromEvaluatingJavaScript:command]; 764 | } 765 | 766 | /** 767 | * Private helper method for converting an NSArray of video IDs into its JavaScript equivalent. 768 | * 769 | * @param videoIds An array of video ID strings to convert into JavaScript format. 770 | * @return A JavaScript array in String format containing video IDs. 771 | */ 772 | - (NSString *)stringFromVideoIdArray:(NSArray *)videoIds { 773 | NSMutableArray *formattedVideoIds = [[NSMutableArray alloc] init]; 774 | 775 | for (id unformattedId in videoIds) { 776 | [formattedVideoIds addObject:[NSString stringWithFormat:@"'%@'", unformattedId]]; 777 | } 778 | 779 | return [NSString stringWithFormat:@"[%@]", [formattedVideoIds componentsJoinedByString:@", "]]; 780 | } 781 | 782 | /** 783 | * Private method for evaluating JavaScript in the WebView. 784 | * 785 | * @param jsToExecute The JavaScript code in string format that we want to execute. 786 | * @return JavaScript response from evaluating code. 787 | */ 788 | - (NSString *)stringFromEvaluatingJavaScript:(NSString *)jsToExecute { 789 | return [self.webView stringByEvaluatingJavaScriptFromString:jsToExecute]; 790 | } 791 | 792 | /** 793 | * Private method to convert a Objective-C BOOL value to JS boolean value. 794 | * 795 | * @param boolValue Objective-C BOOL value. 796 | * @return JavaScript Boolean value, i.e. "true" or "false". 797 | */ 798 | - (NSString *)stringForJSBoolean:(BOOL)boolValue { 799 | return boolValue ? @"true" : @"false"; 800 | } 801 | 802 | #pragma mark Exposed for Testing 803 | - (void)setWebView:(UIWebView *)webView { 804 | _webView = webView; 805 | } 806 | 807 | - (UIWebView *)createNewWebView { 808 | UIWebView *webView = [[UIWebView alloc] initWithFrame:self.bounds]; 809 | webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); 810 | webView.scrollView.scrollEnabled = NO; 811 | webView.scrollView.bounces = NO; 812 | return webView; 813 | } 814 | 815 | - (void)removeWebView { 816 | [self.webView removeFromSuperview]; 817 | self.webView = nil; 818 | } 819 | 820 | @end 821 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #A simple iOS demo for YouTube API 2 | 3 | This is a simple demo that shows you how to integrate your app with YouTube API. You can 4 | find the full tutorial here: 5 | 6 | http://www.appcoda.com/youtube-api-ios-tutorial 7 | 8 | To run the demo, you will need to create your own API key first and update the following line of code in ViewController.swift: 9 | 10 | var apiKey = "YOUR_API_KEY_HERE" 11 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C0C1EDEE1B3E8D9C00E70ABF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C1EDED1B3E8D9C00E70ABF /* AppDelegate.swift */; }; 11 | C0C1EDF01B3E8D9C00E70ABF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C1EDEF1B3E8D9C00E70ABF /* ViewController.swift */; }; 12 | C0C1EDF31B3E8D9C00E70ABF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0C1EDF11B3E8D9C00E70ABF /* Main.storyboard */; }; 13 | C0C1EDF51B3E8D9C00E70ABF /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0C1EDF41B3E8D9C00E70ABF /* Images.xcassets */; }; 14 | C0C1EDF81B3E8D9C00E70ABF /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = C0C1EDF61B3E8D9C00E70ABF /* LaunchScreen.xib */; }; 15 | C0C1EE041B3E8D9C00E70ABF /* YTDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C1EE031B3E8D9C00E70ABF /* YTDemoTests.swift */; }; 16 | C0C1EE0E1B3E8EB900E70ABF /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0C1EE0D1B3E8EB900E70ABF /* PlayerViewController.swift */; }; 17 | C0C1EE131B3E909000E70ABF /* Assets in Resources */ = {isa = PBXBuildFile; fileRef = C0C1EE121B3E909000E70ABF /* Assets */; }; 18 | C0C1EE161B3E97D900E70ABF /* YTPlayerView.m in Sources */ = {isa = PBXBuildFile; fileRef = C0C1EE151B3E97D900E70ABF /* YTPlayerView.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | C0C1EDFE1B3E8D9C00E70ABF /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = C0C1EDE01B3E8D9C00E70ABF /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = C0C1EDE71B3E8D9C00E70ABF; 27 | remoteInfo = YTDemo; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | C0C1EDE81B3E8D9C00E70ABF /* YTDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YTDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | C0C1EDEC1B3E8D9C00E70ABF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | C0C1EDED1B3E8D9C00E70ABF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | C0C1EDEF1B3E8D9C00E70ABF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 36 | C0C1EDF21B3E8D9C00E70ABF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | C0C1EDF41B3E8D9C00E70ABF /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | C0C1EDF71B3E8D9C00E70ABF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 39 | C0C1EDFD1B3E8D9C00E70ABF /* YTDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YTDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | C0C1EE021B3E8D9C00E70ABF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | C0C1EE031B3E8D9C00E70ABF /* YTDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YTDemoTests.swift; sourceTree = ""; }; 42 | C0C1EE0D1B3E8EB900E70ABF /* PlayerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = ""; }; 43 | C0C1EE0F1B3E907600E70ABF /* YTDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "YTDemo-Bridging-Header.h"; sourceTree = ""; }; 44 | C0C1EE121B3E909000E70ABF /* Assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Assets; sourceTree = ""; }; 45 | C0C1EE141B3E97D200E70ABF /* YTPlayerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YTPlayerView.h; sourceTree = ""; }; 46 | C0C1EE151B3E97D900E70ABF /* YTPlayerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YTPlayerView.m; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | C0C1EDE51B3E8D9C00E70ABF /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | C0C1EDFA1B3E8D9C00E70ABF /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | C0C1EDDF1B3E8D9C00E70ABF = { 68 | isa = PBXGroup; 69 | children = ( 70 | C0C1EDEA1B3E8D9C00E70ABF /* YTDemo */, 71 | C0C1EE001B3E8D9C00E70ABF /* YTDemoTests */, 72 | C0C1EDE91B3E8D9C00E70ABF /* Products */, 73 | C0C1EE121B3E909000E70ABF /* Assets */, 74 | C0C1EE141B3E97D200E70ABF /* YTPlayerView.h */, 75 | C0C1EE151B3E97D900E70ABF /* YTPlayerView.m */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | C0C1EDE91B3E8D9C00E70ABF /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | C0C1EDE81B3E8D9C00E70ABF /* YTDemo.app */, 83 | C0C1EDFD1B3E8D9C00E70ABF /* YTDemoTests.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | C0C1EDEA1B3E8D9C00E70ABF /* YTDemo */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | C0C1EDED1B3E8D9C00E70ABF /* AppDelegate.swift */, 92 | C0C1EDEF1B3E8D9C00E70ABF /* ViewController.swift */, 93 | C0C1EE0D1B3E8EB900E70ABF /* PlayerViewController.swift */, 94 | C0C1EDF11B3E8D9C00E70ABF /* Main.storyboard */, 95 | C0C1EDF41B3E8D9C00E70ABF /* Images.xcassets */, 96 | C0C1EDF61B3E8D9C00E70ABF /* LaunchScreen.xib */, 97 | C0C1EDEB1B3E8D9C00E70ABF /* Supporting Files */, 98 | C0C1EE0F1B3E907600E70ABF /* YTDemo-Bridging-Header.h */, 99 | ); 100 | path = YTDemo; 101 | sourceTree = ""; 102 | }; 103 | C0C1EDEB1B3E8D9C00E70ABF /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | C0C1EDEC1B3E8D9C00E70ABF /* Info.plist */, 107 | ); 108 | name = "Supporting Files"; 109 | sourceTree = ""; 110 | }; 111 | C0C1EE001B3E8D9C00E70ABF /* YTDemoTests */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | C0C1EE031B3E8D9C00E70ABF /* YTDemoTests.swift */, 115 | C0C1EE011B3E8D9C00E70ABF /* Supporting Files */, 116 | ); 117 | path = YTDemoTests; 118 | sourceTree = ""; 119 | }; 120 | C0C1EE011B3E8D9C00E70ABF /* Supporting Files */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | C0C1EE021B3E8D9C00E70ABF /* Info.plist */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | C0C1EDE71B3E8D9C00E70ABF /* YTDemo */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = C0C1EE071B3E8D9C00E70ABF /* Build configuration list for PBXNativeTarget "YTDemo" */; 134 | buildPhases = ( 135 | C0C1EDE41B3E8D9C00E70ABF /* Sources */, 136 | C0C1EDE51B3E8D9C00E70ABF /* Frameworks */, 137 | C0C1EDE61B3E8D9C00E70ABF /* Resources */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = YTDemo; 144 | productName = YTDemo; 145 | productReference = C0C1EDE81B3E8D9C00E70ABF /* YTDemo.app */; 146 | productType = "com.apple.product-type.application"; 147 | }; 148 | C0C1EDFC1B3E8D9C00E70ABF /* YTDemoTests */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = C0C1EE0A1B3E8D9C00E70ABF /* Build configuration list for PBXNativeTarget "YTDemoTests" */; 151 | buildPhases = ( 152 | C0C1EDF91B3E8D9C00E70ABF /* Sources */, 153 | C0C1EDFA1B3E8D9C00E70ABF /* Frameworks */, 154 | C0C1EDFB1B3E8D9C00E70ABF /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | C0C1EDFF1B3E8D9C00E70ABF /* PBXTargetDependency */, 160 | ); 161 | name = YTDemoTests; 162 | productName = YTDemoTests; 163 | productReference = C0C1EDFD1B3E8D9C00E70ABF /* YTDemoTests.xctest */; 164 | productType = "com.apple.product-type.bundle.unit-test"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | C0C1EDE01B3E8D9C00E70ABF /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastSwiftUpdateCheck = 0710; 173 | LastUpgradeCheck = 0710; 174 | ORGANIZATIONNAME = Appcoda; 175 | TargetAttributes = { 176 | C0C1EDE71B3E8D9C00E70ABF = { 177 | CreatedOnToolsVersion = 6.3; 178 | }; 179 | C0C1EDFC1B3E8D9C00E70ABF = { 180 | CreatedOnToolsVersion = 6.3; 181 | TestTargetID = C0C1EDE71B3E8D9C00E70ABF; 182 | }; 183 | }; 184 | }; 185 | buildConfigurationList = C0C1EDE31B3E8D9C00E70ABF /* Build configuration list for PBXProject "YTDemo" */; 186 | compatibilityVersion = "Xcode 3.2"; 187 | developmentRegion = English; 188 | hasScannedForEncodings = 0; 189 | knownRegions = ( 190 | en, 191 | Base, 192 | ); 193 | mainGroup = C0C1EDDF1B3E8D9C00E70ABF; 194 | productRefGroup = C0C1EDE91B3E8D9C00E70ABF /* Products */; 195 | projectDirPath = ""; 196 | projectRoot = ""; 197 | targets = ( 198 | C0C1EDE71B3E8D9C00E70ABF /* YTDemo */, 199 | C0C1EDFC1B3E8D9C00E70ABF /* YTDemoTests */, 200 | ); 201 | }; 202 | /* End PBXProject section */ 203 | 204 | /* Begin PBXResourcesBuildPhase section */ 205 | C0C1EDE61B3E8D9C00E70ABF /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | C0C1EDF31B3E8D9C00E70ABF /* Main.storyboard in Resources */, 210 | C0C1EE131B3E909000E70ABF /* Assets in Resources */, 211 | C0C1EDF81B3E8D9C00E70ABF /* LaunchScreen.xib in Resources */, 212 | C0C1EDF51B3E8D9C00E70ABF /* Images.xcassets in Resources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | C0C1EDFB1B3E8D9C00E70ABF /* Resources */ = { 217 | isa = PBXResourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXResourcesBuildPhase section */ 224 | 225 | /* Begin PBXSourcesBuildPhase section */ 226 | C0C1EDE41B3E8D9C00E70ABF /* Sources */ = { 227 | isa = PBXSourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | C0C1EDF01B3E8D9C00E70ABF /* ViewController.swift in Sources */, 231 | C0C1EDEE1B3E8D9C00E70ABF /* AppDelegate.swift in Sources */, 232 | C0C1EE161B3E97D900E70ABF /* YTPlayerView.m in Sources */, 233 | C0C1EE0E1B3E8EB900E70ABF /* PlayerViewController.swift in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | C0C1EDF91B3E8D9C00E70ABF /* Sources */ = { 238 | isa = PBXSourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | C0C1EE041B3E8D9C00E70ABF /* YTDemoTests.swift in Sources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXSourcesBuildPhase section */ 246 | 247 | /* Begin PBXTargetDependency section */ 248 | C0C1EDFF1B3E8D9C00E70ABF /* PBXTargetDependency */ = { 249 | isa = PBXTargetDependency; 250 | target = C0C1EDE71B3E8D9C00E70ABF /* YTDemo */; 251 | targetProxy = C0C1EDFE1B3E8D9C00E70ABF /* PBXContainerItemProxy */; 252 | }; 253 | /* End PBXTargetDependency section */ 254 | 255 | /* Begin PBXVariantGroup section */ 256 | C0C1EDF11B3E8D9C00E70ABF /* Main.storyboard */ = { 257 | isa = PBXVariantGroup; 258 | children = ( 259 | C0C1EDF21B3E8D9C00E70ABF /* Base */, 260 | ); 261 | name = Main.storyboard; 262 | sourceTree = ""; 263 | }; 264 | C0C1EDF61B3E8D9C00E70ABF /* LaunchScreen.xib */ = { 265 | isa = PBXVariantGroup; 266 | children = ( 267 | C0C1EDF71B3E8D9C00E70ABF /* Base */, 268 | ); 269 | name = LaunchScreen.xib; 270 | sourceTree = ""; 271 | }; 272 | /* End PBXVariantGroup section */ 273 | 274 | /* Begin XCBuildConfiguration section */ 275 | C0C1EE051B3E8D9C00E70ABF /* Debug */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_CONSTANT_CONVERSION = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_EMPTY_BODY = YES; 287 | CLANG_WARN_ENUM_CONVERSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_UNREACHABLE_CODE = YES; 291 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 292 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 293 | COPY_PHASE_STRIP = NO; 294 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 295 | ENABLE_STRICT_OBJC_MSGSEND = YES; 296 | ENABLE_TESTABILITY = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_DYNAMIC_NO_PIC = NO; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_OPTIMIZATION_LEVEL = 0; 301 | GCC_PREPROCESSOR_DEFINITIONS = ( 302 | "DEBUG=1", 303 | "$(inherited)", 304 | ); 305 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 306 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 307 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 308 | GCC_WARN_UNDECLARED_SELECTOR = YES; 309 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 310 | GCC_WARN_UNUSED_FUNCTION = YES; 311 | GCC_WARN_UNUSED_VARIABLE = YES; 312 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 313 | MTL_ENABLE_DEBUG_INFO = YES; 314 | ONLY_ACTIVE_ARCH = YES; 315 | SDKROOT = iphoneos; 316 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 317 | }; 318 | name = Debug; 319 | }; 320 | C0C1EE061B3E8D9C00E70ABF /* Release */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 325 | CLANG_CXX_LIBRARY = "libc++"; 326 | CLANG_ENABLE_MODULES = YES; 327 | CLANG_ENABLE_OBJC_ARC = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 340 | ENABLE_NS_ASSERTIONS = NO; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_NO_COMMON_BLOCKS = YES; 344 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 345 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 346 | GCC_WARN_UNDECLARED_SELECTOR = YES; 347 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 348 | GCC_WARN_UNUSED_FUNCTION = YES; 349 | GCC_WARN_UNUSED_VARIABLE = YES; 350 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 351 | MTL_ENABLE_DEBUG_INFO = NO; 352 | SDKROOT = iphoneos; 353 | VALIDATE_PRODUCT = YES; 354 | }; 355 | name = Release; 356 | }; 357 | C0C1EE081B3E8D9C00E70ABF /* Debug */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 361 | CLANG_ENABLE_MODULES = YES; 362 | INFOPLIST_FILE = YTDemo/Info.plist; 363 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 364 | PRODUCT_BUNDLE_IDENTIFIER = "com.appcoda.$(PRODUCT_NAME:rfc1034identifier)"; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | SWIFT_OBJC_BRIDGING_HEADER = "YTDemo/YTDemo-Bridging-Header.h"; 367 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 368 | }; 369 | name = Debug; 370 | }; 371 | C0C1EE091B3E8D9C00E70ABF /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | CLANG_ENABLE_MODULES = YES; 376 | INFOPLIST_FILE = YTDemo/Info.plist; 377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 378 | PRODUCT_BUNDLE_IDENTIFIER = "com.appcoda.$(PRODUCT_NAME:rfc1034identifier)"; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SWIFT_OBJC_BRIDGING_HEADER = "YTDemo/YTDemo-Bridging-Header.h"; 381 | }; 382 | name = Release; 383 | }; 384 | C0C1EE0B1B3E8D9C00E70ABF /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | BUNDLE_LOADER = "$(TEST_HOST)"; 388 | FRAMEWORK_SEARCH_PATHS = ( 389 | "$(SDKROOT)/Developer/Library/Frameworks", 390 | "$(inherited)", 391 | ); 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | INFOPLIST_FILE = YTDemoTests/Info.plist; 397 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 398 | PRODUCT_BUNDLE_IDENTIFIER = "com.appcoda.$(PRODUCT_NAME:rfc1034identifier)"; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YTDemo.app/YTDemo"; 401 | }; 402 | name = Debug; 403 | }; 404 | C0C1EE0C1B3E8D9C00E70ABF /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | BUNDLE_LOADER = "$(TEST_HOST)"; 408 | FRAMEWORK_SEARCH_PATHS = ( 409 | "$(SDKROOT)/Developer/Library/Frameworks", 410 | "$(inherited)", 411 | ); 412 | INFOPLIST_FILE = YTDemoTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = "com.appcoda.$(PRODUCT_NAME:rfc1034identifier)"; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YTDemo.app/YTDemo"; 417 | }; 418 | name = Release; 419 | }; 420 | /* End XCBuildConfiguration section */ 421 | 422 | /* Begin XCConfigurationList section */ 423 | C0C1EDE31B3E8D9C00E70ABF /* Build configuration list for PBXProject "YTDemo" */ = { 424 | isa = XCConfigurationList; 425 | buildConfigurations = ( 426 | C0C1EE051B3E8D9C00E70ABF /* Debug */, 427 | C0C1EE061B3E8D9C00E70ABF /* Release */, 428 | ); 429 | defaultConfigurationIsVisible = 0; 430 | defaultConfigurationName = Release; 431 | }; 432 | C0C1EE071B3E8D9C00E70ABF /* Build configuration list for PBXNativeTarget "YTDemo" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | C0C1EE081B3E8D9C00E70ABF /* Debug */, 436 | C0C1EE091B3E8D9C00E70ABF /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | C0C1EE0A1B3E8D9C00E70ABF /* Build configuration list for PBXNativeTarget "YTDemoTests" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | C0C1EE0B1B3E8D9C00E70ABF /* Debug */, 445 | C0C1EE0C1B3E8D9C00E70ABF /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | /* End XCConfigurationList section */ 451 | }; 452 | rootObject = C0C1EDE01B3E8D9C00E70ABF /* Project object */; 453 | } 454 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/YouTube-API-Demo/0ed82a1d01747bc7cb395a15b7219dbe0b11236c/YTDemo.xcodeproj/project.xcworkspace/xcuserdata/gabriel.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YTDemo.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appcoda/YouTube-API-Demo/0ed82a1d01747bc7cb395a15b7219dbe0b11236c/YTDemo.xcodeproj/project.xcworkspace/xcuserdata/simon.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /YTDemo.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcschemes/YTDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/xcuserdata/gabriel.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YTDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C0C1EDE71B3E8D9C00E70ABF 16 | 17 | primary 18 | 19 | 20 | C0C1EDFC1B3E8D9C00E70ABF 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/YTDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /YTDemo.xcodeproj/xcuserdata/simon.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | YTDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C0C1EDE71B3E8D9C00E70ABF 16 | 17 | primary 18 | 19 | 20 | C0C1EDFC1B3E8D9C00E70ABF 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /YTDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YTDemo 4 | // 5 | // Created by Gabriel Theodoropoulos on 27/6/15. 6 | // Copyright (c) 2015 Appcoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /YTDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /YTDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | -------------------------------------------------------------------------------- /YTDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /YTDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /YTDemo/PlayerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerViewController.swift 3 | // YTDemo 4 | // 5 | // Created by Gabriel Theodoropoulos on 27/6/15. 6 | // Copyright (c) 2015 Appcoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PlayerViewController: UIViewController { 12 | 13 | @IBOutlet weak var playerView: YTPlayerView! 14 | 15 | var videoID: String! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | 22 | playerView.loadWithVideoId(videoID) 23 | } 24 | 25 | override func didReceiveMemoryWarning() { 26 | super.didReceiveMemoryWarning() 27 | // Dispose of any resources that can be recreated. 28 | } 29 | 30 | 31 | /* 32 | // MARK: - Navigation 33 | 34 | // In a storyboard-based application, you will often want to do a little preparation before navigation 35 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 36 | // Get the new view controller using segue.destinationViewController. 37 | // Pass the selected object to the new view controller. 38 | } 39 | */ 40 | 41 | } 42 | -------------------------------------------------------------------------------- /YTDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // YTDemo 4 | // 5 | // Created by Gabriel Theodoropoulos on 27/6/15. 6 | // Copyright (c) 2015 Appcoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate { 12 | 13 | @IBOutlet weak var tblVideos: UITableView! 14 | 15 | @IBOutlet weak var segDisplayedContent: UISegmentedControl! 16 | 17 | @IBOutlet weak var viewWait: UIView! 18 | 19 | @IBOutlet weak var txtSearch: UITextField! 20 | 21 | 22 | var apiKey = "YOUR_API_KEY_HERE" 23 | 24 | var desiredChannelsArray = ["Apple", "Google", "Microsoft"] 25 | 26 | var channelIndex = 0 27 | 28 | var channelsDataArray: Array> = [] 29 | 30 | var videosArray: Array> = [] 31 | 32 | var selectedVideoIndex: Int! 33 | 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | // Do any additional setup after loading the view, typically from a nib. 38 | 39 | tblVideos.delegate = self 40 | tblVideos.dataSource = self 41 | txtSearch.delegate = self 42 | 43 | getChannelDetails(false) 44 | } 45 | 46 | override func didReceiveMemoryWarning() { 47 | super.didReceiveMemoryWarning() 48 | // Dispose of any resources that can be recreated. 49 | } 50 | 51 | 52 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 53 | if segue.identifier == "idSeguePlayer" { 54 | let playerViewController = segue.destinationViewController as! PlayerViewController 55 | playerViewController.videoID = videosArray[selectedVideoIndex]["videoID"] as! String 56 | } 57 | } 58 | 59 | 60 | // MARK: IBAction method implementation 61 | 62 | @IBAction func changeContent(sender: AnyObject) { 63 | tblVideos.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Fade) 64 | } 65 | 66 | 67 | // MARK: UITableView method implementation 68 | 69 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 70 | return 1 71 | } 72 | 73 | 74 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 75 | if segDisplayedContent.selectedSegmentIndex == 0 { 76 | return channelsDataArray.count 77 | } 78 | else { 79 | return videosArray.count 80 | } 81 | } 82 | 83 | 84 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 85 | var cell: UITableViewCell! 86 | 87 | if segDisplayedContent.selectedSegmentIndex == 0 { 88 | cell = tableView.dequeueReusableCellWithIdentifier("idCellChannel", forIndexPath: indexPath) 89 | 90 | let channelTitleLabel = cell.viewWithTag(10) as! UILabel 91 | let channelDescriptionLabel = cell.viewWithTag(11) as! UILabel 92 | let thumbnailImageView = cell.viewWithTag(12) as! UIImageView 93 | 94 | let channelDetails = channelsDataArray[indexPath.row] 95 | channelTitleLabel.text = channelDetails["title"] as? String 96 | channelDescriptionLabel.text = channelDetails["description"] as? String 97 | thumbnailImageView.image = UIImage(data: NSData(contentsOfURL: NSURL(string: (channelDetails["thumbnail"] as? String)!)!)!) 98 | } 99 | else { 100 | cell = tableView.dequeueReusableCellWithIdentifier("idCellVideo", forIndexPath: indexPath) 101 | 102 | let videoTitle = cell.viewWithTag(10) as! UILabel 103 | let videoThumbnail = cell.viewWithTag(11) as! UIImageView 104 | 105 | let videoDetails = videosArray[indexPath.row] 106 | videoTitle.text = videoDetails["title"] as? String 107 | videoThumbnail.image = UIImage(data: NSData(contentsOfURL: NSURL(string: (videoDetails["thumbnail"] as? String)!)!)!) 108 | } 109 | 110 | return cell 111 | } 112 | 113 | 114 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 115 | return 140.0 116 | } 117 | 118 | 119 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 120 | if segDisplayedContent.selectedSegmentIndex == 0 { 121 | // In this case the channels are the displayed content. 122 | // The videos of the selected channel should be fetched and displayed. 123 | 124 | // Switch the segmented control to "Videos". 125 | segDisplayedContent.selectedSegmentIndex = 1 126 | 127 | // Show the activity indicator. 128 | viewWait.hidden = false 129 | 130 | // Remove all existing video details from the videosArray array. 131 | videosArray.removeAll(keepCapacity: false) 132 | 133 | // Fetch the video details for the tapped channel. 134 | getVideosForChannelAtIndex(indexPath.row) 135 | } 136 | else { 137 | selectedVideoIndex = indexPath.row 138 | performSegueWithIdentifier("idSeguePlayer", sender: self) 139 | } 140 | } 141 | 142 | 143 | // MARK: UITextFieldDelegate method implementation 144 | 145 | func textFieldShouldReturn(textField: UITextField) -> Bool { 146 | textField.resignFirstResponder() 147 | viewWait.hidden = false 148 | 149 | // Specify the search type (channel, video). 150 | var type = "channel" 151 | if segDisplayedContent.selectedSegmentIndex == 1 { 152 | type = "video" 153 | videosArray.removeAll(keepCapacity: false) 154 | } 155 | 156 | // Form the request URL string. 157 | var urlString = "https://www.googleapis.com/youtube/v3/search?part=snippet&q=\(textField.text)&type=\(type)&key=\(apiKey)" 158 | urlString = urlString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())! 159 | 160 | // Create a NSURL object based on the above string. 161 | let targetURL = NSURL(string: urlString) 162 | 163 | // Get the results. 164 | performGetRequest(targetURL, completion: { (data, HTTPStatusCode, error) -> Void in 165 | if HTTPStatusCode == 200 && error == nil { 166 | // Convert the JSON data to a dictionary object. 167 | do { 168 | let resultsDict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! Dictionary 169 | 170 | // Get all search result items ("items" array). 171 | let items: Array> = resultsDict["items"] as! Array> 172 | 173 | // Loop through all search results and keep just the necessary data. 174 | for var i=0; i 176 | 177 | // Gather the proper data depending on whether we're searching for channels or for videos. 178 | if self.segDisplayedContent.selectedSegmentIndex == 0 { 179 | // Keep the channel ID. 180 | self.desiredChannelsArray.append(snippetDict["channelId"] as! String) 181 | } 182 | else { 183 | // Create a new dictionary to store the video details. 184 | var videoDetailsDict = Dictionary() 185 | videoDetailsDict["title"] = snippetDict["title"] 186 | videoDetailsDict["thumbnail"] = ((snippetDict["thumbnails"] as! Dictionary)["default"] as! Dictionary)["url"] 187 | videoDetailsDict["videoID"] = (items[i]["id"] as! Dictionary)["videoId"] 188 | 189 | // Append the desiredPlaylistItemDataDict dictionary to the videos array. 190 | self.videosArray.append(videoDetailsDict) 191 | 192 | // Reload the tableview. 193 | self.tblVideos.reloadData() 194 | } 195 | } 196 | } catch { 197 | print(error) 198 | } 199 | 200 | // Call the getChannelDetails(…) function to fetch the channels. 201 | if self.segDisplayedContent.selectedSegmentIndex == 0 { 202 | self.getChannelDetails(true) 203 | } 204 | 205 | } 206 | else { 207 | print("HTTP Status Code = \(HTTPStatusCode)") 208 | print("Error while loading channel videos: \(error)") 209 | } 210 | 211 | // Hide the activity indicator. 212 | self.viewWait.hidden = true 213 | }) 214 | 215 | 216 | return true 217 | } 218 | 219 | 220 | // MARK: Custom method implementation 221 | 222 | func performGetRequest(targetURL: NSURL!, completion: (data: NSData?, HTTPStatusCode: Int, error: NSError?) -> Void) { 223 | let request = NSMutableURLRequest(URL: targetURL) 224 | request.HTTPMethod = "GET" 225 | 226 | let sessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration() 227 | 228 | let session = NSURLSession(configuration: sessionConfiguration) 229 | 230 | let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in 231 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 232 | completion(data: data, HTTPStatusCode: (response as! NSHTTPURLResponse).statusCode, error: error) 233 | }) 234 | }) 235 | 236 | task.resume() 237 | } 238 | 239 | 240 | func getChannelDetails(useChannelIDParam: Bool) { 241 | var urlString: String! 242 | if !useChannelIDParam { 243 | urlString = "https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&forUsername=\(desiredChannelsArray[channelIndex])&key=\(apiKey)" 244 | } 245 | else { 246 | urlString = "https://www.googleapis.com/youtube/v3/channels?part=contentDetails,snippet&id=\(desiredChannelsArray[channelIndex])&key=\(apiKey)" 247 | } 248 | 249 | let targetURL = NSURL(string: urlString) 250 | 251 | performGetRequest(targetURL, completion: { (data, HTTPStatusCode, error) -> Void in 252 | if HTTPStatusCode == 200 && error == nil { 253 | 254 | do { 255 | // Convert the JSON data to a dictionary. 256 | let resultsDict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! Dictionary 257 | 258 | // Get the first dictionary item from the returned items (usually there's just one item). 259 | let items: AnyObject! = resultsDict["items"] as AnyObject! 260 | let firstItemDict = (items as! Array)[0] as! Dictionary 261 | 262 | // Get the snippet dictionary that contains the desired data. 263 | let snippetDict = firstItemDict["snippet"] as! Dictionary 264 | 265 | // Create a new dictionary to store only the values we care about. 266 | var desiredValuesDict: Dictionary = Dictionary() 267 | desiredValuesDict["title"] = snippetDict["title"] 268 | desiredValuesDict["description"] = snippetDict["description"] 269 | desiredValuesDict["thumbnail"] = ((snippetDict["thumbnails"] as! Dictionary)["default"] as! Dictionary)["url"] 270 | 271 | // Save the channel's uploaded videos playlist ID. 272 | desiredValuesDict["playlistID"] = ((firstItemDict["contentDetails"] as! Dictionary)["relatedPlaylists"] as! Dictionary)["uploads"] 273 | 274 | 275 | // Append the desiredValuesDict dictionary to the following array. 276 | self.channelsDataArray.append(desiredValuesDict) 277 | 278 | 279 | // Reload the tableview. 280 | self.tblVideos.reloadData() 281 | 282 | // Load the next channel data (if exist). 283 | ++self.channelIndex 284 | if self.channelIndex < self.desiredChannelsArray.count { 285 | self.getChannelDetails(useChannelIDParam) 286 | } 287 | else { 288 | self.viewWait.hidden = true 289 | } 290 | } catch { 291 | print(error) 292 | } 293 | 294 | } else { 295 | print("HTTP Status Code = \(HTTPStatusCode)") 296 | print("Error while loading channel details: \(error)") 297 | } 298 | }) 299 | } 300 | 301 | 302 | func getVideosForChannelAtIndex(index: Int!) { 303 | // Get the selected channel's playlistID value from the channelsDataArray array and use it for fetching the proper video playlst. 304 | let playlistID = channelsDataArray[index]["playlistID"] as! String 305 | 306 | // Form the request URL string. 307 | let urlString = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=\(playlistID)&key=\(apiKey)" 308 | 309 | // Create a NSURL object based on the above string. 310 | let targetURL = NSURL(string: urlString) 311 | 312 | // Fetch the playlist from Google. 313 | performGetRequest(targetURL, completion: { (data, HTTPStatusCode, error) -> Void in 314 | if HTTPStatusCode == 200 && error == nil { 315 | do { 316 | // Convert the JSON data into a dictionary. 317 | let resultsDict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! Dictionary 318 | 319 | // Get all playlist items ("items" array). 320 | let items: Array> = resultsDict["items"] as! Array> 321 | 322 | // Use a loop to go through all video items. 323 | for var i=0; i)["snippet"] as! Dictionary 325 | 326 | // Initialize a new dictionary and store the data of interest. 327 | var desiredPlaylistItemDataDict = Dictionary() 328 | 329 | desiredPlaylistItemDataDict["title"] = playlistSnippetDict["title"] 330 | desiredPlaylistItemDataDict["thumbnail"] = ((playlistSnippetDict["thumbnails"] as! Dictionary)["default"] as! Dictionary)["url"] 331 | desiredPlaylistItemDataDict["videoID"] = (playlistSnippetDict["resourceId"] as! Dictionary)["videoId"] 332 | 333 | // Append the desiredPlaylistItemDataDict dictionary to the videos array. 334 | self.videosArray.append(desiredPlaylistItemDataDict) 335 | 336 | // Reload the tableview. 337 | self.tblVideos.reloadData() 338 | } 339 | } catch { 340 | print(error) 341 | } 342 | } 343 | else { 344 | print("HTTP Status Code = \(HTTPStatusCode)") 345 | print("Error while loading channel videos: \(error)") 346 | } 347 | 348 | // Hide the activity indicator. 349 | self.viewWait.hidden = true 350 | }) 351 | } 352 | } 353 | 354 | -------------------------------------------------------------------------------- /YTDemo/YTDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "YTPlayerView.h" -------------------------------------------------------------------------------- /YTDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /YTDemoTests/YTDemoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YTDemoTests.swift 3 | // YTDemoTests 4 | // 5 | // Created by Gabriel Theodoropoulos on 27/6/15. 6 | // Copyright (c) 2015 Appcoda. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class YTDemoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /YTPlayerView.h: -------------------------------------------------------------------------------- 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 | 15 | #import 16 | 17 | @class YTPlayerView; 18 | 19 | /** These enums represent the state of the current video in the player. */ 20 | typedef NS_ENUM(NSInteger, YTPlayerState) { 21 | kYTPlayerStateUnstarted, 22 | kYTPlayerStateEnded, 23 | kYTPlayerStatePlaying, 24 | kYTPlayerStatePaused, 25 | kYTPlayerStateBuffering, 26 | kYTPlayerStateQueued, 27 | kYTPlayerStateUnknown 28 | }; 29 | 30 | /** These enums represent the resolution of the currently loaded video. */ 31 | typedef NS_ENUM(NSInteger, YTPlaybackQuality) { 32 | kYTPlaybackQualitySmall, 33 | kYTPlaybackQualityMedium, 34 | kYTPlaybackQualityLarge, 35 | kYTPlaybackQualityHD720, 36 | kYTPlaybackQualityHD1080, 37 | kYTPlaybackQualityHighRes, 38 | kYTPlaybackQualityAuto, /** Addition for YouTube Live Events. */ 39 | kYTPlaybackQualityDefault, 40 | kYTPlaybackQualityUnknown /** This should never be returned. It is here for future proofing. */ 41 | }; 42 | 43 | /** These enums represent error codes thrown by the player. */ 44 | typedef NS_ENUM(NSInteger, YTPlayerError) { 45 | kYTPlayerErrorInvalidParam, 46 | kYTPlayerErrorHTML5Error, 47 | kYTPlayerErrorVideoNotFound, // Functionally equivalent error codes 100 and 48 | // 105 have been collapsed into |kYTPlayerErrorVideoNotFound|. 49 | kYTPlayerErrorNotEmbeddable, // Functionally equivalent error codes 101 and 50 | // 150 have been collapsed into |kYTPlayerErrorNotEmbeddable|. 51 | kYTPlayerErrorUnknown 52 | }; 53 | 54 | /** 55 | * A delegate for ViewControllers to respond to YouTube player events outside 56 | * of the view, such as changes to video playback state or playback errors. 57 | * The callback functions correlate to the events fired by the IFrame API. 58 | * For the full documentation, see the IFrame documentation here: 59 | * https://developers.google.com/youtube/iframe_api_reference#Events 60 | */ 61 | @protocol YTPlayerViewDelegate 62 | 63 | @optional 64 | /** 65 | * Invoked when the player view is ready to receive API calls. 66 | * 67 | * @param playerView The YTPlayerView instance that has become ready. 68 | */ 69 | - (void)playerViewDidBecomeReady:(YTPlayerView *)playerView; 70 | 71 | /** 72 | * Callback invoked when player state has changed, e.g. stopped or started playback. 73 | * 74 | * @param playerView The YTPlayerView instance where playback state has changed. 75 | * @param state YTPlayerState designating the new playback state. 76 | */ 77 | - (void)playerView:(YTPlayerView *)playerView didChangeToState:(YTPlayerState)state; 78 | 79 | /** 80 | * Callback invoked when playback quality has changed. 81 | * 82 | * @param playerView The YTPlayerView instance where playback quality has changed. 83 | * @param quality YTPlaybackQuality designating the new playback quality. 84 | */ 85 | - (void)playerView:(YTPlayerView *)playerView didChangeToQuality:(YTPlaybackQuality)quality; 86 | 87 | /** 88 | * Callback invoked when an error has occured. 89 | * 90 | * @param playerView The YTPlayerView instance where the error has occurred. 91 | * @param error YTPlayerError containing the error state. 92 | */ 93 | - (void)playerView:(YTPlayerView *)playerView receivedError:(YTPlayerError)error; 94 | 95 | 96 | /** 97 | * Callback invoked frequently when playBack is plaing. 98 | * 99 | * @param playerView The YTPlayerView instance where the error has occurred. 100 | * @param playTime float containing curretn playback time. 101 | */ 102 | - (void)playerView:(YTPlayerView *)playerView didPlayTime:(float)playTime; 103 | 104 | @end 105 | 106 | /** 107 | * YTPlayerView is a custom UIView that client developers will use to include YouTube 108 | * videos in their iOS applications. It can be instantiated programmatically, or via 109 | * Interface Builder. Use the methods YTPlayerView::loadWithVideoId:, 110 | * YTPlayerView::loadWithPlaylistId: or their variants to set the video or playlist 111 | * to populate the view with. 112 | */ 113 | @interface YTPlayerView : UIView 114 | 115 | @property(nonatomic, strong, readonly) UIWebView *webView; 116 | 117 | /** A delegate to be notified on playback events. */ 118 | @property(nonatomic, weak) id delegate; 119 | 120 | /** 121 | * This method loads the player with the given video ID. 122 | * This is a convenience method for calling YTPlayerView::loadPlayerWithVideoId:withPlayerVars: 123 | * without player variables. 124 | * 125 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 126 | * To change the currently loaded video without reloading the entire UIWebView, use the 127 | * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. 128 | * 129 | * @param videoId The YouTube video ID of the video to load in the player view. 130 | * @return YES if player has been configured correctly, NO otherwise. 131 | */ 132 | - (BOOL)loadWithVideoId:(NSString *)videoId; 133 | 134 | /** 135 | * This method loads the player with the given playlist ID. 136 | * This is a convenience method for calling YTPlayerView::loadWithPlaylistId:withPlayerVars: 137 | * without player variables. 138 | * 139 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 140 | * To change the currently loaded video without reloading the entire UIWebView, use the 141 | * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: 142 | * family of methods. 143 | * 144 | * @param playlistId The YouTube playlist ID of the playlist to load in the player view. 145 | * @return YES if player has been configured correctly, NO otherwise. 146 | */ 147 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId; 148 | 149 | /** 150 | * This method loads the player with the given video ID and player variables. Player variables 151 | * specify optional parameters for video playback. For instance, to play a YouTube 152 | * video inline, the following playerVars dictionary would be used: 153 | * 154 | * @code 155 | * @{ @"playsinline" : @1 }; 156 | * @endcode 157 | * 158 | * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), 159 | * both strings and integers are valid values. The full list of parameters is defined at: 160 | * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. 161 | * 162 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 163 | * To change the currently loaded video without reloading the entire UIWebView, use the 164 | * YTPlayerView::cueVideoById:startSeconds:suggestedQuality: family of methods. 165 | * 166 | * @param videoId The YouTube video ID of the video to load in the player view. 167 | * @param playerVars An NSDictionary of player parameters. 168 | * @return YES if player has been configured correctly, NO otherwise. 169 | */ 170 | - (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars; 171 | 172 | /** 173 | * This method loads the player with the given playlist ID and player variables. Player variables 174 | * specify optional parameters for video playback. For instance, to play a YouTube 175 | * video inline, the following playerVars dictionary would be used: 176 | * 177 | * @code 178 | * @{ @"playsinline" : @1 }; 179 | * @endcode 180 | * 181 | * Note that when the documentation specifies a valid value as a number (typically 0, 1 or 2), 182 | * both strings and integers are valid values. The full list of parameters is defined at: 183 | * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5. 184 | * 185 | * This method reloads the entire contents of the UIWebView and regenerates its HTML contents. 186 | * To change the currently loaded video without reloading the entire UIWebView, use the 187 | * YTPlayerView::cuePlaylistByPlaylistId:index:startSeconds:suggestedQuality: 188 | * family of methods. 189 | * 190 | * @param playlistId The YouTube playlist ID of the playlist to load in the player view. 191 | * @param playerVars An NSDictionary of player parameters. 192 | * @return YES if player has been configured correctly, NO otherwise. 193 | */ 194 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars; 195 | 196 | - (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams; 197 | 198 | #pragma mark - Player controls 199 | 200 | // These methods correspond to their JavaScript equivalents as documented here: 201 | // https://developers.google.com/youtube/iframe_api_reference#Playback_controls 202 | 203 | /** 204 | * Starts or resumes playback on the loaded video. Corresponds to this method from 205 | * the JavaScript API: 206 | * https://developers.google.com/youtube/iframe_api_reference#playVideo 207 | */ 208 | - (void)playVideo; 209 | 210 | /** 211 | * Pauses playback on a playing video. Corresponds to this method from 212 | * the JavaScript API: 213 | * https://developers.google.com/youtube/iframe_api_reference#pauseVideo 214 | */ 215 | - (void)pauseVideo; 216 | 217 | /** 218 | * Stops playback on a playing video. Corresponds to this method from 219 | * the JavaScript API: 220 | * https://developers.google.com/youtube/iframe_api_reference#stopVideo 221 | */ 222 | - (void)stopVideo; 223 | 224 | /** 225 | * Seek to a given time on a playing video. Corresponds to this method from 226 | * the JavaScript API: 227 | * https://developers.google.com/youtube/iframe_api_reference#seekTo 228 | * 229 | * @param seekToSeconds The time in seconds to seek to in the loaded video. 230 | * @param allowSeekAhead Whether to make a new request to the server if the time is 231 | * outside what is currently buffered. Recommended to set to YES. 232 | */ 233 | - (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead; 234 | 235 | /** 236 | * Clears the loaded video from the player. Corresponds to this method from 237 | * the JavaScript API: 238 | * https://developers.google.com/youtube/iframe_api_reference#clearVideo 239 | */ 240 | - (void)clearVideo; 241 | 242 | #pragma mark - Queuing videos 243 | 244 | // Queueing functions for videos. These methods correspond to their JavaScript 245 | // equivalents as documented here: 246 | // https://developers.google.com/youtube/iframe_api_reference#Queueing_Functions 247 | 248 | /** 249 | * Cues a given video by its video ID for playback starting at the given time and with the 250 | * suggested quality. Cueing loads a video, but does not start video playback. This method 251 | * corresponds with its JavaScript API equivalent as documented here: 252 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoById 253 | * 254 | * @param videoId A video ID to cue. 255 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 256 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 257 | */ 258 | - (void)cueVideoById:(NSString *)videoId 259 | startSeconds:(float)startSeconds 260 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 261 | 262 | /** 263 | * Cues a given video by its video ID for playback starting and ending at the given times 264 | * with the suggested quality. Cueing loads a video, but does not start video playback. This 265 | * method corresponds with its JavaScript API equivalent as documented here: 266 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoById 267 | * 268 | * @param videoId A video ID to cue. 269 | * @param startSeconds Time in seconds to start the video when playVideo() is called. 270 | * @param endSeconds Time in seconds to end the video after it begins playing. 271 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 272 | */ 273 | - (void)cueVideoById:(NSString *)videoId 274 | startSeconds:(float)startSeconds 275 | endSeconds:(float)endSeconds 276 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 277 | 278 | /** 279 | * Loads a given video by its video ID for playback starting at the given time and with the 280 | * suggested quality. Loading a video both loads it and begins playback. This method 281 | * corresponds with its JavaScript API equivalent as documented here: 282 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoById 283 | * 284 | * @param videoId A video ID to load and begin playing. 285 | * @param startSeconds Time in seconds to start the video when it has loaded. 286 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 287 | */ 288 | - (void)loadVideoById:(NSString *)videoId 289 | startSeconds:(float)startSeconds 290 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 291 | 292 | /** 293 | * Loads a given video by its video ID for playback starting and ending at the given times 294 | * with the suggested quality. Loading a video both loads it and begins playback. This method 295 | * corresponds with its JavaScript API equivalent as documented here: 296 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoById 297 | * 298 | * @param videoId A video ID to load and begin playing. 299 | * @param startSeconds Time in seconds to start the video when it has loaded. 300 | * @param endSeconds Time in seconds to end the video after it begins playing. 301 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 302 | */ 303 | - (void)loadVideoById:(NSString *)videoId 304 | startSeconds:(float)startSeconds 305 | endSeconds:(float)endSeconds 306 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 307 | 308 | /** 309 | * Cues a given video by its URL on YouTube.com for playback starting at the given time 310 | * and with the suggested quality. Cueing loads a video, but does not start video playback. 311 | * This method corresponds with its JavaScript API equivalent as documented here: 312 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl 313 | * 314 | * @param videoURL URL of a YouTube video to cue for playback. 315 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 316 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 317 | */ 318 | - (void)cueVideoByURL:(NSString *)videoURL 319 | startSeconds:(float)startSeconds 320 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 321 | 322 | /** 323 | * Cues a given video by its URL on YouTube.com for playback starting at the given time 324 | * and with the suggested quality. Cueing loads a video, but does not start video playback. 325 | * This method corresponds with its JavaScript API equivalent as documented here: 326 | * https://developers.google.com/youtube/iframe_api_reference#cueVideoByUrl 327 | * 328 | * @param videoURL URL of a YouTube video to cue for playback. 329 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 330 | * @param endSeconds Time in seconds to end the video after it begins playing. 331 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 332 | */ 333 | - (void)cueVideoByURL:(NSString *)videoURL 334 | startSeconds:(float)startSeconds 335 | endSeconds:(float)endSeconds 336 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 337 | 338 | /** 339 | * Loads a given video by its video ID for playback starting at the given time 340 | * with the suggested quality. Loading a video both loads it and begins playback. This method 341 | * corresponds with its JavaScript API equivalent as documented here: 342 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl 343 | * 344 | * @param videoURL URL of a YouTube video to load and play. 345 | * @param startSeconds Time in seconds to start the video when it has loaded. 346 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 347 | */ 348 | - (void)loadVideoByURL:(NSString *)videoURL 349 | startSeconds:(float)startSeconds 350 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 351 | 352 | /** 353 | * Loads a given video by its video ID for playback starting and ending at the given times 354 | * with the suggested quality. Loading a video both loads it and begins playback. This method 355 | * corresponds with its JavaScript API equivalent as documented here: 356 | * https://developers.google.com/youtube/iframe_api_reference#loadVideoByUrl 357 | * 358 | * @param videoURL URL of a YouTube video to load and play. 359 | * @param startSeconds Time in seconds to start the video when it has loaded. 360 | * @param endSeconds Time in seconds to end the video after it begins playing. 361 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 362 | */ 363 | - (void)loadVideoByURL:(NSString *)videoURL 364 | startSeconds:(float)startSeconds 365 | endSeconds:(float)endSeconds 366 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 367 | 368 | #pragma mark - Queuing functions for playlists 369 | 370 | // Queueing functions for playlists. These methods correspond to 371 | // the JavaScript methods defined here: 372 | // https://developers.google.com/youtube/js_api_reference#Playlist_Queueing_Functions 373 | 374 | /** 375 | * Cues a given playlist with the given ID. The |index| parameter specifies the 0-indexed 376 | * position of the first video to play, starting at the given time and with the 377 | * suggested quality. Cueing loads a playlist, but does not start video playback. This method 378 | * corresponds with its JavaScript API equivalent as documented here: 379 | * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist 380 | * 381 | * @param playlistId Playlist ID of a YouTube playlist to cue. 382 | * @param index A 0-indexed position specifying the first video to play. 383 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 384 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 385 | */ 386 | - (void)cuePlaylistByPlaylistId:(NSString *)playlistId 387 | index:(int)index 388 | startSeconds:(float)startSeconds 389 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 390 | 391 | /** 392 | * Cues a playlist of videos with the given video IDs. The |index| parameter specifies the 393 | * 0-indexed position of the first video to play, starting at the given time and with the 394 | * suggested quality. Cueing loads a playlist, but does not start video playback. This method 395 | * corresponds with its JavaScript API equivalent as documented here: 396 | * https://developers.google.com/youtube/iframe_api_reference#cuePlaylist 397 | * 398 | * @param videoIds An NSArray of video IDs to compose the playlist of. 399 | * @param index A 0-indexed position specifying the first video to play. 400 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 401 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 402 | */ 403 | - (void)cuePlaylistByVideos:(NSArray *)videoIds 404 | index:(int)index 405 | startSeconds:(float)startSeconds 406 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 407 | 408 | /** 409 | * Loads a given playlist with the given ID. The |index| parameter specifies the 0-indexed 410 | * position of the first video to play, starting at the given time and with the 411 | * suggested quality. Loading a playlist starts video playback. This method 412 | * corresponds with its JavaScript API equivalent as documented here: 413 | * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist 414 | * 415 | * @param playlistId Playlist ID of a YouTube playlist to cue. 416 | * @param index A 0-indexed position specifying the first video to play. 417 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 418 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 419 | */ 420 | - (void)loadPlaylistByPlaylistId:(NSString *)playlistId 421 | index:(int)index 422 | startSeconds:(float)startSeconds 423 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 424 | 425 | /** 426 | * Loads a playlist of videos with the given video IDs. The |index| parameter specifies the 427 | * 0-indexed position of the first video to play, starting at the given time and with the 428 | * suggested quality. Loading a playlist starts video playback. This method 429 | * corresponds with its JavaScript API equivalent as documented here: 430 | * https://developers.google.com/youtube/iframe_api_reference#loadPlaylist 431 | * 432 | * @param videoIds An NSArray of video IDs to compose the playlist of. 433 | * @param index A 0-indexed position specifying the first video to play. 434 | * @param startSeconds Time in seconds to start the video when YTPlayerView::playVideo is called. 435 | * @param suggestedQuality YTPlaybackQuality value suggesting a playback quality. 436 | */ 437 | - (void)loadPlaylistByVideos:(NSArray *)videoIds 438 | index:(int)index 439 | startSeconds:(float)startSeconds 440 | suggestedQuality:(YTPlaybackQuality)suggestedQuality; 441 | 442 | #pragma mark - Playing a video in a playlist 443 | 444 | // These methods correspond to the JavaScript API as defined under the 445 | // "Playing a video in a playlist" section here: 446 | // https://developers.google.com/youtube/iframe_api_reference#Playback_status 447 | 448 | /** 449 | * Loads and plays the next video in the playlist. Corresponds to this method from 450 | * the JavaScript API: 451 | * https://developers.google.com/youtube/iframe_api_reference#nextVideo 452 | */ 453 | - (void)nextVideo; 454 | 455 | /** 456 | * Loads and plays the previous video in the playlist. Corresponds to this method from 457 | * the JavaScript API: 458 | * https://developers.google.com/youtube/iframe_api_reference#previousVideo 459 | */ 460 | - (void)previousVideo; 461 | 462 | /** 463 | * Loads and plays the video at the given 0-indexed position in the playlist. 464 | * Corresponds to this method from the JavaScript API: 465 | * https://developers.google.com/youtube/iframe_api_reference#playVideoAt 466 | * 467 | * @param index The 0-indexed position of the video in the playlist to load and play. 468 | */ 469 | - (void)playVideoAt:(int)index; 470 | 471 | #pragma mark - Setting the playback rate 472 | 473 | /** 474 | * Gets the playback rate. The default value is 1.0, which represents a video 475 | * playing at normal speed. Other values may include 0.25 or 0.5 for slower 476 | * speeds, and 1.5 or 2.0 for faster speeds. This method corresponds to the 477 | * JavaScript API defined here: 478 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate 479 | * 480 | * @return An integer value between 0 and 100 representing the current volume. 481 | */ 482 | - (float)playbackRate; 483 | 484 | /** 485 | * Sets the playback rate. The default value is 1.0, which represents a video 486 | * playing at normal speed. Other values may include 0.25 or 0.5 for slower 487 | * speeds, and 1.5 or 2.0 for faster speeds. To fetch a list of valid values for 488 | * this method, call YTPlayerView::getAvailablePlaybackRates. This method does not 489 | * guarantee that the playback rate will change. 490 | * This method corresponds to the JavaScript API defined here: 491 | * https://developers.google.com/youtube/iframe_api_reference#setPlaybackRate 492 | * 493 | * @param suggestedRate A playback rate to suggest for the player. 494 | */ 495 | - (void)setPlaybackRate:(float)suggestedRate; 496 | 497 | /** 498 | * Gets a list of the valid playback rates, useful in conjunction with 499 | * YTPlayerView::setPlaybackRate. This method corresponds to the 500 | * JavaScript API defined here: 501 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackRate 502 | * 503 | * @return An NSArray containing available playback rates. nil if there is an error. 504 | */ 505 | - (NSArray *)availablePlaybackRates; 506 | 507 | #pragma mark - Setting playback behavior for playlists 508 | 509 | /** 510 | * Sets whether the player should loop back to the first video in the playlist 511 | * after it has finished playing the last video. This method corresponds to the 512 | * JavaScript API defined here: 513 | * https://developers.google.com/youtube/iframe_api_reference#loopPlaylist 514 | * 515 | * @param loop A boolean representing whether the player should loop. 516 | */ 517 | - (void)setLoop:(BOOL)loop; 518 | 519 | /** 520 | * Sets whether the player should shuffle through the playlist. This method 521 | * corresponds to the JavaScript API defined here: 522 | * https://developers.google.com/youtube/iframe_api_reference#shufflePlaylist 523 | * 524 | * @param shuffle A boolean representing whether the player should 525 | * shuffle through the playlist. 526 | */ 527 | - (void)setShuffle:(BOOL)shuffle; 528 | 529 | #pragma mark - Playback status 530 | // These methods correspond to the JavaScript methods defined here: 531 | // https://developers.google.com/youtube/js_api_reference#Playback_status 532 | 533 | /** 534 | * Returns a number between 0 and 1 that specifies the percentage of the video 535 | * that the player shows as buffered. This method corresponds to the 536 | * JavaScript API defined here: 537 | * https://developers.google.com/youtube/iframe_api_reference#getVideoLoadedFraction 538 | * 539 | * @return A float value between 0 and 1 representing the percentage of the video 540 | * already loaded. 541 | */ 542 | - (float)videoLoadedFraction; 543 | 544 | /** 545 | * Returns the state of the player. This method corresponds to the 546 | * JavaScript API defined here: 547 | * https://developers.google.com/youtube/iframe_api_reference#getPlayerState 548 | * 549 | * @return |YTPlayerState| representing the state of the player. 550 | */ 551 | - (YTPlayerState)playerState; 552 | 553 | /** 554 | * Returns the elapsed time in seconds since the video started playing. This 555 | * method corresponds to the JavaScript API defined here: 556 | * https://developers.google.com/youtube/iframe_api_reference#getCurrentTime 557 | * 558 | * @return Time in seconds since the video started playing. 559 | */ 560 | - (float)currentTime; 561 | 562 | #pragma mark - Playback quality 563 | 564 | // Playback quality. These methods correspond to the JavaScript 565 | // methods defined here: 566 | // https://developers.google.com/youtube/js_api_reference#Playback_quality 567 | 568 | /** 569 | * Returns the playback quality. This method corresponds to the 570 | * JavaScript API defined here: 571 | * https://developers.google.com/youtube/iframe_api_reference#getPlaybackQuality 572 | * 573 | * @return YTPlaybackQuality representing the current playback quality. 574 | */ 575 | - (YTPlaybackQuality)playbackQuality; 576 | 577 | /** 578 | * Suggests playback quality for the video. It is recommended to leave this setting to 579 | * |default|. This method corresponds to the JavaScript API defined here: 580 | * https://developers.google.com/youtube/iframe_api_reference#setPlaybackQuality 581 | * 582 | * @param quality YTPlaybackQuality value to suggest for the player. 583 | */ 584 | - (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality; 585 | 586 | /** 587 | * Gets a list of the valid playback quality values, useful in conjunction with 588 | * YTPlayerView::setPlaybackQuality. This method corresponds to the 589 | * JavaScript API defined here: 590 | * https://developers.google.com/youtube/iframe_api_reference#getAvailableQualityLevels 591 | * 592 | * @return An NSArray containing available playback quality levels. 593 | */ 594 | - (NSArray *)availableQualityLevels; 595 | 596 | #pragma mark - Retrieving video information 597 | 598 | // Retrieving video information. These methods correspond to the JavaScript 599 | // methods defined here: 600 | // https://developers.google.com/youtube/js_api_reference#Retrieving_video_information 601 | 602 | /** 603 | * Returns the duration in seconds since the video of the video. This 604 | * method corresponds to the JavaScript API defined here: 605 | * https://developers.google.com/youtube/iframe_api_reference#getDuration 606 | * 607 | * @return Length of the video in seconds. 608 | */ 609 | - (NSTimeInterval)duration; 610 | 611 | /** 612 | * Returns the YouTube.com URL for the video. This method corresponds 613 | * to the JavaScript API defined here: 614 | * https://developers.google.com/youtube/iframe_api_reference#getVideoUrl 615 | * 616 | * @return The YouTube.com URL for the video. 617 | */ 618 | - (NSURL *)videoUrl; 619 | 620 | /** 621 | * Returns the embed code for the current video. This method corresponds 622 | * to the JavaScript API defined here: 623 | * https://developers.google.com/youtube/iframe_api_reference#getVideoEmbedCode 624 | * 625 | * @return The embed code for the current video. 626 | */ 627 | - (NSString *)videoEmbedCode; 628 | 629 | #pragma mark - Retrieving playlist information 630 | 631 | // Retrieving playlist information. These methods correspond to the 632 | // JavaScript defined here: 633 | // https://developers.google.com/youtube/js_api_reference#Retrieving_playlist_information 634 | 635 | /** 636 | * Returns an ordered array of video IDs in the playlist. This method corresponds 637 | * to the JavaScript API defined here: 638 | * https://developers.google.com/youtube/iframe_api_reference#getPlaylist 639 | * 640 | * @return An NSArray containing all the video IDs in the current playlist. |nil| on error. 641 | */ 642 | - (NSArray *)playlist; 643 | 644 | /** 645 | * Returns the 0-based index of the currently playing item in the playlist. 646 | * This method corresponds to the JavaScript API defined here: 647 | * https://developers.google.com/youtube/iframe_api_reference#getPlaylistIndex 648 | * 649 | * @return The 0-based index of the currently playing item in the playlist. 650 | */ 651 | - (int)playlistIndex; 652 | 653 | - (void)removeWebView; 654 | 655 | @end 656 | -------------------------------------------------------------------------------- /YTPlayerView.m: -------------------------------------------------------------------------------- 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 | 15 | #import "YTPlayerView.h" 16 | 17 | // These are instances of NSString because we get them from parsing a URL. It would be silly to 18 | // convert these into an integer just to have to convert the URL query string value into an integer 19 | // as well for the sake of doing a value comparison. A full list of response error codes can be 20 | // found here: 21 | // https://developers.google.com/youtube/iframe_api_reference 22 | NSString static *const kYTPlayerStateUnstartedCode = @"-1"; 23 | NSString static *const kYTPlayerStateEndedCode = @"0"; 24 | NSString static *const kYTPlayerStatePlayingCode = @"1"; 25 | NSString static *const kYTPlayerStatePausedCode = @"2"; 26 | NSString static *const kYTPlayerStateBufferingCode = @"3"; 27 | NSString static *const kYTPlayerStateCuedCode = @"5"; 28 | NSString static *const kYTPlayerStateUnknownCode = @"unknown"; 29 | 30 | // Constants representing playback quality. 31 | NSString static *const kYTPlaybackQualitySmallQuality = @"small"; 32 | NSString static *const kYTPlaybackQualityMediumQuality = @"medium"; 33 | NSString static *const kYTPlaybackQualityLargeQuality = @"large"; 34 | NSString static *const kYTPlaybackQualityHD720Quality = @"hd720"; 35 | NSString static *const kYTPlaybackQualityHD1080Quality = @"hd1080"; 36 | NSString static *const kYTPlaybackQualityHighResQuality = @"highres"; 37 | NSString static *const kYTPlaybackQualityAutoQuality = @"auto"; 38 | NSString static *const kYTPlaybackQualityDefaultQuality = @"default"; 39 | NSString static *const kYTPlaybackQualityUnknownQuality = @"unknown"; 40 | 41 | // Constants representing YouTube player errors. 42 | NSString static *const kYTPlayerErrorInvalidParamErrorCode = @"2"; 43 | NSString static *const kYTPlayerErrorHTML5ErrorCode = @"5"; 44 | NSString static *const kYTPlayerErrorVideoNotFoundErrorCode = @"100"; 45 | NSString static *const kYTPlayerErrorNotEmbeddableErrorCode = @"101"; 46 | NSString static *const kYTPlayerErrorCannotFindVideoErrorCode = @"105"; 47 | NSString static *const kYTPlayerErrorSameAsNotEmbeddableErrorCode = @"150"; 48 | 49 | // Constants representing player callbacks. 50 | NSString static *const kYTPlayerCallbackOnReady = @"onReady"; 51 | NSString static *const kYTPlayerCallbackOnStateChange = @"onStateChange"; 52 | NSString static *const kYTPlayerCallbackOnPlaybackQualityChange = @"onPlaybackQualityChange"; 53 | NSString static *const kYTPlayerCallbackOnError = @"onError"; 54 | NSString static *const kYTPlayerCallbackOnPlayTime = @"onPlayTime"; 55 | 56 | NSString static *const kYTPlayerCallbackOnYouTubeIframeAPIReady = @"onYouTubeIframeAPIReady"; 57 | 58 | NSString static *const kYTPlayerEmbedUrlRegexPattern = @"^http(s)://(www.)youtube.com/embed/(.*)$"; 59 | NSString static *const kYTPlayerAdUrlRegexPattern = @"^http(s)://pubads.g.doubleclick.net/pagead/conversion/"; 60 | NSString static *const kYTPlayerOAuthRegexPattern = @"^http(s)://accounts.google.com/o/oauth2/(.*)$"; 61 | NSString static *const kYTPlayerStaticProxyRegexPattern = @"^https://content.googleapis.com/static/proxy.html(.*)$"; 62 | 63 | @interface YTPlayerView() 64 | 65 | @property(nonatomic, strong) NSURL *originURL; 66 | 67 | @end 68 | 69 | @implementation YTPlayerView 70 | 71 | - (BOOL)loadWithVideoId:(NSString *)videoId { 72 | return [self loadWithVideoId:videoId playerVars:nil]; 73 | } 74 | 75 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId { 76 | return [self loadWithPlaylistId:playlistId playerVars:nil]; 77 | } 78 | 79 | - (BOOL)loadWithVideoId:(NSString *)videoId playerVars:(NSDictionary *)playerVars { 80 | if (!playerVars) { 81 | playerVars = @{}; 82 | } 83 | NSDictionary *playerParams = @{ @"videoId" : videoId, @"playerVars" : playerVars }; 84 | return [self loadWithPlayerParams:playerParams]; 85 | } 86 | 87 | - (BOOL)loadWithPlaylistId:(NSString *)playlistId playerVars:(NSDictionary *)playerVars { 88 | 89 | // Mutable copy because we may have been passed an immutable config dictionary. 90 | NSMutableDictionary *tempPlayerVars = [[NSMutableDictionary alloc] init]; 91 | [tempPlayerVars setValue:@"playlist" forKey:@"listType"]; 92 | [tempPlayerVars setValue:playlistId forKey:@"list"]; 93 | [tempPlayerVars addEntriesFromDictionary:playerVars]; // No-op if playerVars is null 94 | 95 | NSDictionary *playerParams = @{ @"playerVars" : tempPlayerVars }; 96 | return [self loadWithPlayerParams:playerParams]; 97 | } 98 | 99 | #pragma mark - Player methods 100 | 101 | - (void)playVideo { 102 | [self stringFromEvaluatingJavaScript:@"player.playVideo();"]; 103 | } 104 | 105 | - (void)pauseVideo { 106 | [self notifyDelegateOfYouTubeCallbackUrl:[NSURL URLWithString:[NSString stringWithFormat:@"ytplayer://onStateChange?data=%@", kYTPlayerStatePausedCode]]]; 107 | [self stringFromEvaluatingJavaScript:@"player.pauseVideo();"]; 108 | } 109 | 110 | - (void)stopVideo { 111 | [self stringFromEvaluatingJavaScript:@"player.stopVideo();"]; 112 | } 113 | 114 | - (void)seekToSeconds:(float)seekToSeconds allowSeekAhead:(BOOL)allowSeekAhead { 115 | NSNumber *secondsValue = [NSNumber numberWithFloat:seekToSeconds]; 116 | NSString *allowSeekAheadValue = [self stringForJSBoolean:allowSeekAhead]; 117 | NSString *command = [NSString stringWithFormat:@"player.seekTo(%@, %@);", secondsValue, allowSeekAheadValue]; 118 | [self stringFromEvaluatingJavaScript:command]; 119 | } 120 | 121 | - (void)clearVideo { 122 | [self stringFromEvaluatingJavaScript:@"player.clearVideo();"]; 123 | } 124 | 125 | #pragma mark - Cueing methods 126 | 127 | - (void)cueVideoById:(NSString *)videoId 128 | startSeconds:(float)startSeconds 129 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 130 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 131 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 132 | NSString *command = [NSString stringWithFormat:@"player.cueVideoById('%@', %@, '%@');", 133 | videoId, startSecondsValue, qualityValue]; 134 | [self stringFromEvaluatingJavaScript:command]; 135 | } 136 | 137 | - (void)cueVideoById:(NSString *)videoId 138 | startSeconds:(float)startSeconds 139 | endSeconds:(float)endSeconds 140 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 141 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 142 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 143 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 144 | NSString *command = [NSString stringWithFormat:@"player.cueVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});", videoId, startSecondsValue, endSecondsValue, qualityValue]; 145 | [self stringFromEvaluatingJavaScript:command]; 146 | } 147 | 148 | - (void)loadVideoById:(NSString *)videoId 149 | startSeconds:(float)startSeconds 150 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 151 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 152 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 153 | NSString *command = [NSString stringWithFormat:@"player.loadVideoById('%@', %@, '%@');", 154 | videoId, startSecondsValue, qualityValue]; 155 | [self stringFromEvaluatingJavaScript:command]; 156 | } 157 | 158 | - (void)loadVideoById:(NSString *)videoId 159 | startSeconds:(float)startSeconds 160 | endSeconds:(float)endSeconds 161 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 162 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 163 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 164 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 165 | NSString *command = [NSString stringWithFormat:@"player.loadVideoById({'videoId': '%@', 'startSeconds': %@, 'endSeconds': %@, 'suggestedQuality': '%@'});",videoId, startSecondsValue, endSecondsValue, qualityValue]; 166 | [self stringFromEvaluatingJavaScript:command]; 167 | } 168 | 169 | - (void)cueVideoByURL:(NSString *)videoURL 170 | startSeconds:(float)startSeconds 171 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 172 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 173 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 174 | NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, '%@');", 175 | videoURL, startSecondsValue, qualityValue]; 176 | [self stringFromEvaluatingJavaScript:command]; 177 | } 178 | 179 | - (void)cueVideoByURL:(NSString *)videoURL 180 | startSeconds:(float)startSeconds 181 | endSeconds:(float)endSeconds 182 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 183 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 184 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 185 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 186 | NSString *command = [NSString stringWithFormat:@"player.cueVideoByUrl('%@', %@, %@, '%@');", 187 | videoURL, startSecondsValue, endSecondsValue, qualityValue]; 188 | [self stringFromEvaluatingJavaScript:command]; 189 | } 190 | 191 | - (void)loadVideoByURL:(NSString *)videoURL 192 | startSeconds:(float)startSeconds 193 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 194 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 195 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 196 | NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, '%@');", 197 | videoURL, startSecondsValue, qualityValue]; 198 | [self stringFromEvaluatingJavaScript:command]; 199 | } 200 | 201 | - (void)loadVideoByURL:(NSString *)videoURL 202 | startSeconds:(float)startSeconds 203 | endSeconds:(float)endSeconds 204 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 205 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 206 | NSNumber *endSecondsValue = [NSNumber numberWithFloat:endSeconds]; 207 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 208 | NSString *command = [NSString stringWithFormat:@"player.loadVideoByUrl('%@', %@, %@, '%@');", 209 | videoURL, startSecondsValue, endSecondsValue, qualityValue]; 210 | [self stringFromEvaluatingJavaScript:command]; 211 | } 212 | 213 | #pragma mark - Cueing methods for lists 214 | 215 | - (void)cuePlaylistByPlaylistId:(NSString *)playlistId 216 | index:(int)index 217 | startSeconds:(float)startSeconds 218 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 219 | NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; 220 | [self cuePlaylist:playlistIdString 221 | index:index 222 | startSeconds:startSeconds 223 | suggestedQuality:suggestedQuality]; 224 | } 225 | 226 | - (void)cuePlaylistByVideos:(NSArray *)videoIds 227 | index:(int)index 228 | startSeconds:(float)startSeconds 229 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 230 | [self cuePlaylist:[self stringFromVideoIdArray:videoIds] 231 | index:index 232 | startSeconds:startSeconds 233 | suggestedQuality:suggestedQuality]; 234 | } 235 | 236 | - (void)loadPlaylistByPlaylistId:(NSString *)playlistId 237 | index:(int)index 238 | startSeconds:(float)startSeconds 239 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 240 | NSString *playlistIdString = [NSString stringWithFormat:@"'%@'", playlistId]; 241 | [self loadPlaylist:playlistIdString 242 | index:index 243 | startSeconds:startSeconds 244 | suggestedQuality:suggestedQuality]; 245 | } 246 | 247 | - (void)loadPlaylistByVideos:(NSArray *)videoIds 248 | index:(int)index 249 | startSeconds:(float)startSeconds 250 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 251 | [self loadPlaylist:[self stringFromVideoIdArray:videoIds] 252 | index:index 253 | startSeconds:startSeconds 254 | suggestedQuality:suggestedQuality]; 255 | } 256 | 257 | #pragma mark - Setting the playback rate 258 | 259 | - (float)playbackRate { 260 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackRate();"]; 261 | return [returnValue floatValue]; 262 | } 263 | 264 | - (void)setPlaybackRate:(float)suggestedRate { 265 | NSString *command = [NSString stringWithFormat:@"player.setPlaybackRate(%f);", suggestedRate]; 266 | [self stringFromEvaluatingJavaScript:command]; 267 | } 268 | 269 | - (NSArray *)availablePlaybackRates { 270 | NSString *returnValue = 271 | [self stringFromEvaluatingJavaScript:@"player.getAvailablePlaybackRates();"]; 272 | 273 | NSData *playbackRateData = [returnValue dataUsingEncoding:NSUTF8StringEncoding]; 274 | NSError *jsonDeserializationError; 275 | NSArray *playbackRates = [NSJSONSerialization JSONObjectWithData:playbackRateData 276 | options:kNilOptions 277 | error:&jsonDeserializationError]; 278 | if (jsonDeserializationError) { 279 | return nil; 280 | } 281 | 282 | return playbackRates; 283 | } 284 | 285 | #pragma mark - Setting playback behavior for playlists 286 | 287 | - (void)setLoop:(BOOL)loop { 288 | NSString *loopPlayListValue = [self stringForJSBoolean:loop]; 289 | NSString *command = [NSString stringWithFormat:@"player.setLoop(%@);", loopPlayListValue]; 290 | [self stringFromEvaluatingJavaScript:command]; 291 | } 292 | 293 | - (void)setShuffle:(BOOL)shuffle { 294 | NSString *shufflePlayListValue = [self stringForJSBoolean:shuffle]; 295 | NSString *command = [NSString stringWithFormat:@"player.setShuffle(%@);", shufflePlayListValue]; 296 | [self stringFromEvaluatingJavaScript:command]; 297 | } 298 | 299 | #pragma mark - Playback status 300 | 301 | - (float)videoLoadedFraction { 302 | return [[self stringFromEvaluatingJavaScript:@"player.getVideoLoadedFraction();"] floatValue]; 303 | } 304 | 305 | - (YTPlayerState)playerState { 306 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlayerState();"]; 307 | return [YTPlayerView playerStateForString:returnValue]; 308 | } 309 | 310 | - (float)currentTime { 311 | return [[self stringFromEvaluatingJavaScript:@"player.getCurrentTime();"] floatValue]; 312 | } 313 | 314 | // Playback quality 315 | - (YTPlaybackQuality)playbackQuality { 316 | NSString *qualityValue = [self stringFromEvaluatingJavaScript:@"player.getPlaybackQuality();"]; 317 | return [YTPlayerView playbackQualityForString:qualityValue]; 318 | } 319 | 320 | - (void)setPlaybackQuality:(YTPlaybackQuality)suggestedQuality { 321 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 322 | NSString *command = [NSString stringWithFormat:@"player.setPlaybackQuality('%@');", qualityValue]; 323 | [self stringFromEvaluatingJavaScript:command]; 324 | } 325 | 326 | #pragma mark - Video information methods 327 | 328 | - (NSTimeInterval)duration { 329 | return [[self stringFromEvaluatingJavaScript:@"player.getDuration();"] doubleValue]; 330 | } 331 | 332 | - (NSURL *)videoUrl { 333 | return [NSURL URLWithString:[self stringFromEvaluatingJavaScript:@"player.getVideoUrl();"]]; 334 | } 335 | 336 | - (NSString *)videoEmbedCode { 337 | return [self stringFromEvaluatingJavaScript:@"player.getVideoEmbedCode();"]; 338 | } 339 | 340 | #pragma mark - Playlist methods 341 | 342 | - (NSArray *)playlist { 343 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylist();"]; 344 | 345 | NSData *playlistData = [returnValue dataUsingEncoding:NSUTF8StringEncoding]; 346 | NSError *jsonDeserializationError; 347 | NSArray *videoIds = [NSJSONSerialization JSONObjectWithData:playlistData 348 | options:kNilOptions 349 | error:&jsonDeserializationError]; 350 | if (jsonDeserializationError) { 351 | return nil; 352 | } 353 | 354 | return videoIds; 355 | } 356 | 357 | - (int)playlistIndex { 358 | NSString *returnValue = [self stringFromEvaluatingJavaScript:@"player.getPlaylistIndex();"]; 359 | return [returnValue intValue]; 360 | } 361 | 362 | #pragma mark - Playing a video in a playlist 363 | 364 | - (void)nextVideo { 365 | [self stringFromEvaluatingJavaScript:@"player.nextVideo();"]; 366 | } 367 | 368 | - (void)previousVideo { 369 | [self stringFromEvaluatingJavaScript:@"player.previousVideo();"]; 370 | } 371 | 372 | - (void)playVideoAt:(int)index { 373 | NSString *command = 374 | [NSString stringWithFormat:@"player.playVideoAt(%@);", [NSNumber numberWithInt:index]]; 375 | [self stringFromEvaluatingJavaScript:command]; 376 | } 377 | 378 | #pragma mark - Helper methods 379 | 380 | - (NSArray *)availableQualityLevels { 381 | NSString *returnValue = 382 | [self stringFromEvaluatingJavaScript:@"player.getAvailableQualityLevels().toString();"]; 383 | if(!returnValue) return nil; 384 | 385 | NSArray *rawQualityValues = [returnValue componentsSeparatedByString:@","]; 386 | NSMutableArray *levels = [[NSMutableArray alloc] init]; 387 | for (NSString *rawQualityValue in rawQualityValues) { 388 | YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:rawQualityValue]; 389 | [levels addObject:[NSNumber numberWithInt:quality]]; 390 | } 391 | return levels; 392 | } 393 | 394 | - (BOOL)webView:(UIWebView *)webView 395 | shouldStartLoadWithRequest:(NSURLRequest *)request 396 | navigationType:(UIWebViewNavigationType)navigationType { 397 | if ([request.URL.host isEqual: self.originURL.host]) { 398 | return YES; 399 | } else if ([request.URL.scheme isEqual:@"ytplayer"]) { 400 | [self notifyDelegateOfYouTubeCallbackUrl:request.URL]; 401 | return NO; 402 | } else if ([request.URL.scheme isEqual: @"http"] || [request.URL.scheme isEqual:@"https"]) { 403 | return [self handleHttpNavigationToUrl:request.URL]; 404 | } 405 | return YES; 406 | } 407 | 408 | /** 409 | * Convert a quality value from NSString to the typed enum value. 410 | * 411 | * @param qualityString A string representing playback quality. Ex: "small", "medium", "hd1080". 412 | * @return An enum value representing the playback quality. 413 | */ 414 | + (YTPlaybackQuality)playbackQualityForString:(NSString *)qualityString { 415 | YTPlaybackQuality quality = kYTPlaybackQualityUnknown; 416 | 417 | if ([qualityString isEqualToString:kYTPlaybackQualitySmallQuality]) { 418 | quality = kYTPlaybackQualitySmall; 419 | } else if ([qualityString isEqualToString:kYTPlaybackQualityMediumQuality]) { 420 | quality = kYTPlaybackQualityMedium; 421 | } else if ([qualityString isEqualToString:kYTPlaybackQualityLargeQuality]) { 422 | quality = kYTPlaybackQualityLarge; 423 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHD720Quality]) { 424 | quality = kYTPlaybackQualityHD720; 425 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHD1080Quality]) { 426 | quality = kYTPlaybackQualityHD1080; 427 | } else if ([qualityString isEqualToString:kYTPlaybackQualityHighResQuality]) { 428 | quality = kYTPlaybackQualityHighRes; 429 | } else if ([qualityString isEqualToString:kYTPlaybackQualityAutoQuality]) { 430 | quality = kYTPlaybackQualityAuto; 431 | } 432 | 433 | return quality; 434 | } 435 | 436 | /** 437 | * Convert a |YTPlaybackQuality| value from the typed value to NSString. 438 | * 439 | * @param quality A |YTPlaybackQuality| parameter. 440 | * @return An |NSString| value to be used in the JavaScript bridge. 441 | */ 442 | + (NSString *)stringForPlaybackQuality:(YTPlaybackQuality)quality { 443 | switch (quality) { 444 | case kYTPlaybackQualitySmall: 445 | return kYTPlaybackQualitySmallQuality; 446 | case kYTPlaybackQualityMedium: 447 | return kYTPlaybackQualityMediumQuality; 448 | case kYTPlaybackQualityLarge: 449 | return kYTPlaybackQualityLargeQuality; 450 | case kYTPlaybackQualityHD720: 451 | return kYTPlaybackQualityHD720Quality; 452 | case kYTPlaybackQualityHD1080: 453 | return kYTPlaybackQualityHD1080Quality; 454 | case kYTPlaybackQualityHighRes: 455 | return kYTPlaybackQualityHighResQuality; 456 | case kYTPlaybackQualityAuto: 457 | return kYTPlaybackQualityAutoQuality; 458 | default: 459 | return kYTPlaybackQualityUnknownQuality; 460 | } 461 | } 462 | 463 | /** 464 | * Convert a state value from NSString to the typed enum value. 465 | * 466 | * @param stateString A string representing player state. Ex: "-1", "0", "1". 467 | * @return An enum value representing the player state. 468 | */ 469 | + (YTPlayerState)playerStateForString:(NSString *)stateString { 470 | YTPlayerState state = kYTPlayerStateUnknown; 471 | if ([stateString isEqualToString:kYTPlayerStateUnstartedCode]) { 472 | state = kYTPlayerStateUnstarted; 473 | } else if ([stateString isEqualToString:kYTPlayerStateEndedCode]) { 474 | state = kYTPlayerStateEnded; 475 | } else if ([stateString isEqualToString:kYTPlayerStatePlayingCode]) { 476 | state = kYTPlayerStatePlaying; 477 | } else if ([stateString isEqualToString:kYTPlayerStatePausedCode]) { 478 | state = kYTPlayerStatePaused; 479 | } else if ([stateString isEqualToString:kYTPlayerStateBufferingCode]) { 480 | state = kYTPlayerStateBuffering; 481 | } else if ([stateString isEqualToString:kYTPlayerStateCuedCode]) { 482 | state = kYTPlayerStateQueued; 483 | } 484 | return state; 485 | } 486 | 487 | /** 488 | * Convert a state value from the typed value to NSString. 489 | * 490 | * @param quality A |YTPlayerState| parameter. 491 | * @return A string value to be used in the JavaScript bridge. 492 | */ 493 | + (NSString *)stringForPlayerState:(YTPlayerState)state { 494 | switch (state) { 495 | case kYTPlayerStateUnstarted: 496 | return kYTPlayerStateUnstartedCode; 497 | case kYTPlayerStateEnded: 498 | return kYTPlayerStateEndedCode; 499 | case kYTPlayerStatePlaying: 500 | return kYTPlayerStatePlayingCode; 501 | case kYTPlayerStatePaused: 502 | return kYTPlayerStatePausedCode; 503 | case kYTPlayerStateBuffering: 504 | return kYTPlayerStateBufferingCode; 505 | case kYTPlayerStateQueued: 506 | return kYTPlayerStateCuedCode; 507 | default: 508 | return kYTPlayerStateUnknownCode; 509 | } 510 | } 511 | 512 | #pragma mark - Private methods 513 | 514 | /** 515 | * Private method to handle "navigation" to a callback URL of the format 516 | * ytplayer://action?data=someData 517 | * This is how the UIWebView communicates with the containing Objective-C code. 518 | * Side effects of this method are that it calls methods on this class's delegate. 519 | * 520 | * @param url A URL of the format ytplayer://action?data=value. 521 | */ 522 | - (void)notifyDelegateOfYouTubeCallbackUrl: (NSURL *) url { 523 | NSString *action = url.host; 524 | 525 | // We know the query can only be of the format ytplayer://action?data=SOMEVALUE, 526 | // so we parse out the value. 527 | NSString *query = url.query; 528 | NSString *data; 529 | if (query) { 530 | data = [query componentsSeparatedByString:@"="][1]; 531 | } 532 | 533 | if ([action isEqual:kYTPlayerCallbackOnReady]) { 534 | if ([self.delegate respondsToSelector:@selector(playerViewDidBecomeReady:)]) { 535 | [self.delegate playerViewDidBecomeReady:self]; 536 | } 537 | } else if ([action isEqual:kYTPlayerCallbackOnStateChange]) { 538 | if ([self.delegate respondsToSelector:@selector(playerView:didChangeToState:)]) { 539 | YTPlayerState state = kYTPlayerStateUnknown; 540 | 541 | if ([data isEqual:kYTPlayerStateEndedCode]) { 542 | state = kYTPlayerStateEnded; 543 | } else if ([data isEqual:kYTPlayerStatePlayingCode]) { 544 | state = kYTPlayerStatePlaying; 545 | } else if ([data isEqual:kYTPlayerStatePausedCode]) { 546 | state = kYTPlayerStatePaused; 547 | } else if ([data isEqual:kYTPlayerStateBufferingCode]) { 548 | state = kYTPlayerStateBuffering; 549 | } else if ([data isEqual:kYTPlayerStateCuedCode]) { 550 | state = kYTPlayerStateQueued; 551 | } else if ([data isEqual:kYTPlayerStateUnstartedCode]) { 552 | state = kYTPlayerStateUnstarted; 553 | } 554 | 555 | [self.delegate playerView:self didChangeToState:state]; 556 | } 557 | } else if ([action isEqual:kYTPlayerCallbackOnPlaybackQualityChange]) { 558 | if ([self.delegate respondsToSelector:@selector(playerView:didChangeToQuality:)]) { 559 | YTPlaybackQuality quality = [YTPlayerView playbackQualityForString:data]; 560 | [self.delegate playerView:self didChangeToQuality:quality]; 561 | } 562 | } else if ([action isEqual:kYTPlayerCallbackOnError]) { 563 | if ([self.delegate respondsToSelector:@selector(playerView:receivedError:)]) { 564 | YTPlayerError error = kYTPlayerErrorUnknown; 565 | 566 | if ([data isEqual:kYTPlayerErrorInvalidParamErrorCode]) { 567 | error = kYTPlayerErrorInvalidParam; 568 | } else if ([data isEqual:kYTPlayerErrorHTML5ErrorCode]) { 569 | error = kYTPlayerErrorHTML5Error; 570 | } else if ([data isEqual:kYTPlayerErrorNotEmbeddableErrorCode] || 571 | [data isEqual:kYTPlayerErrorSameAsNotEmbeddableErrorCode]) { 572 | error = kYTPlayerErrorNotEmbeddable; 573 | } else if ([data isEqual:kYTPlayerErrorVideoNotFoundErrorCode] || 574 | [data isEqual:kYTPlayerErrorCannotFindVideoErrorCode]) { 575 | error = kYTPlayerErrorVideoNotFound; 576 | } 577 | 578 | [self.delegate playerView:self receivedError:error]; 579 | } 580 | } else if ([action isEqualToString:kYTPlayerCallbackOnPlayTime]) { 581 | if ([self.delegate respondsToSelector:@selector(playerView:didPlayTime:)]) { 582 | float time = [data floatValue]; 583 | [self.delegate playerView:self didPlayTime:time]; 584 | } 585 | 586 | } 587 | } 588 | 589 | - (BOOL)handleHttpNavigationToUrl:(NSURL *) url { 590 | // Usually this means the user has clicked on the YouTube logo or an error message in the 591 | // player. Most URLs should open in the browser. The only http(s) URL that should open in this 592 | // UIWebView is the URL for the embed, which is of the format: 593 | // http(s)://www.youtube.com/embed/[VIDEO ID]?[PARAMETERS] 594 | NSError *error = NULL; 595 | NSRegularExpression *ytRegex = 596 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerEmbedUrlRegexPattern 597 | options:NSRegularExpressionCaseInsensitive 598 | error:&error]; 599 | NSTextCheckingResult *ytMatch = 600 | [ytRegex firstMatchInString:url.absoluteString 601 | options:0 602 | range:NSMakeRange(0, [url.absoluteString length])]; 603 | 604 | NSRegularExpression *adRegex = 605 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerAdUrlRegexPattern 606 | options:NSRegularExpressionCaseInsensitive 607 | error:&error]; 608 | NSTextCheckingResult *adMatch = 609 | [adRegex firstMatchInString:url.absoluteString 610 | options:0 611 | range:NSMakeRange(0, [url.absoluteString length])]; 612 | 613 | NSRegularExpression *oauthRegex = 614 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerOAuthRegexPattern 615 | options:NSRegularExpressionCaseInsensitive 616 | error:&error]; 617 | NSTextCheckingResult *oauthMatch = 618 | [oauthRegex firstMatchInString:url.absoluteString 619 | options:0 620 | range:NSMakeRange(0, [url.absoluteString length])]; 621 | 622 | NSRegularExpression *staticProxyRegex = 623 | [NSRegularExpression regularExpressionWithPattern:kYTPlayerStaticProxyRegexPattern 624 | options:NSRegularExpressionCaseInsensitive 625 | error:&error]; 626 | NSTextCheckingResult *staticProxyMatch = 627 | [staticProxyRegex firstMatchInString:url.absoluteString 628 | options:0 629 | range:NSMakeRange(0, [url.absoluteString length])]; 630 | 631 | if (ytMatch || adMatch || oauthMatch || staticProxyMatch) { 632 | return YES; 633 | } else { 634 | [[UIApplication sharedApplication] openURL:url]; 635 | return NO; 636 | } 637 | } 638 | 639 | 640 | /** 641 | * Private helper method to load an iframe player with the given player parameters. 642 | * 643 | * @param additionalPlayerParams An NSDictionary of parameters in addition to required parameters 644 | * to instantiate the HTML5 player with. This differs depending on 645 | * whether a single video or playlist is being loaded. 646 | * @return YES if successful, NO if not. 647 | */ 648 | - (BOOL)loadWithPlayerParams:(NSDictionary *)additionalPlayerParams { 649 | NSDictionary *playerCallbacks = @{ 650 | @"onReady" : @"onReady", 651 | @"onStateChange" : @"onStateChange", 652 | @"onPlaybackQualityChange" : @"onPlaybackQualityChange", 653 | @"onError" : @"onPlayerError" 654 | }; 655 | NSMutableDictionary *playerParams = [[NSMutableDictionary alloc] init]; 656 | [playerParams addEntriesFromDictionary:additionalPlayerParams]; 657 | if (![playerParams objectForKey:@"height"]) { 658 | [playerParams setValue:@"100%" forKey:@"height"]; 659 | } 660 | if (![playerParams objectForKey:@"width"]) { 661 | [playerParams setValue:@"100%" forKey:@"width"]; 662 | } 663 | 664 | [playerParams setValue:playerCallbacks forKey:@"events"]; 665 | 666 | if ([playerParams objectForKey:@"playerVars"]) { 667 | NSMutableDictionary *playerVars = [[NSMutableDictionary alloc] init]; 668 | [playerVars addEntriesFromDictionary:[playerParams objectForKey:@"playerVars"]]; 669 | 670 | if (![playerVars objectForKey:@"origin"]) { 671 | self.originURL = [NSURL URLWithString:@"about:blank"]; 672 | } else { 673 | self.originURL = [NSURL URLWithString: [playerVars objectForKey:@"origin"]]; 674 | } 675 | } else { 676 | // This must not be empty so we can render a '{}' in the output JSON 677 | [playerParams setValue:[[NSDictionary alloc] init] forKey:@"playerVars"]; 678 | } 679 | 680 | // Remove the existing webView to reset any state 681 | [self.webView removeFromSuperview]; 682 | _webView = [self createNewWebView]; 683 | [self addSubview:self.webView]; 684 | 685 | NSError *error = nil; 686 | NSString *path = [[NSBundle mainBundle] pathForResource:@"YTPlayerView-iframe-player" 687 | ofType:@"html" 688 | inDirectory:@"Assets"]; 689 | NSString *embedHTMLTemplate = 690 | [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; 691 | 692 | if (error) { 693 | NSLog(@"Received error rendering template: %@", error); 694 | return NO; 695 | } 696 | 697 | // Render the playerVars as a JSON dictionary. 698 | NSError *jsonRenderingError = nil; 699 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:playerParams 700 | options:NSJSONWritingPrettyPrinted 701 | error:&jsonRenderingError]; 702 | if (jsonRenderingError) { 703 | NSLog(@"Attempted configuration of player with invalid playerVars: %@ \tError: %@", 704 | playerParams, 705 | jsonRenderingError); 706 | return NO; 707 | } 708 | 709 | NSString *playerVarsJsonString = 710 | [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 711 | 712 | NSString *embedHTML = [NSString stringWithFormat:embedHTMLTemplate, playerVarsJsonString]; 713 | [self.webView loadHTMLString:embedHTML baseURL: self.originURL]; 714 | [self.webView setDelegate:self]; 715 | self.webView.allowsInlineMediaPlayback = YES; 716 | self.webView.mediaPlaybackRequiresUserAction = NO; 717 | return YES; 718 | } 719 | 720 | /** 721 | * Private method for cueing both cases of playlist ID and array of video IDs. Cueing 722 | * a playlist does not start playback. 723 | * 724 | * @param cueingString A JavaScript string representing an array, playlist ID or list of 725 | * video IDs to play with the playlist player. 726 | * @param index 0-index position of video to start playback on. 727 | * @param startSeconds Seconds after start of video to begin playback. 728 | * @param suggestedQuality Suggested YTPlaybackQuality to play the videos. 729 | * @return The result of cueing the playlist. 730 | */ 731 | - (void)cuePlaylist:(NSString *)cueingString 732 | index:(int)index 733 | startSeconds:(float)startSeconds 734 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 735 | NSNumber *indexValue = [NSNumber numberWithInt:index]; 736 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 737 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 738 | NSString *command = [NSString stringWithFormat:@"player.cuePlaylist(%@, %@, %@, '%@');", 739 | cueingString, indexValue, startSecondsValue, qualityValue]; 740 | [self stringFromEvaluatingJavaScript:command]; 741 | } 742 | 743 | /** 744 | * Private method for loading both cases of playlist ID and array of video IDs. Loading 745 | * a playlist automatically starts playback. 746 | * 747 | * @param cueingString A JavaScript string representing an array, playlist ID or list of 748 | * video IDs to play with the playlist player. 749 | * @param index 0-index position of video to start playback on. 750 | * @param startSeconds Seconds after start of video to begin playback. 751 | * @param suggestedQuality Suggested YTPlaybackQuality to play the videos. 752 | * @return The result of cueing the playlist. 753 | */ 754 | - (void)loadPlaylist:(NSString *)cueingString 755 | index:(int)index 756 | startSeconds:(float)startSeconds 757 | suggestedQuality:(YTPlaybackQuality)suggestedQuality { 758 | NSNumber *indexValue = [NSNumber numberWithInt:index]; 759 | NSNumber *startSecondsValue = [NSNumber numberWithFloat:startSeconds]; 760 | NSString *qualityValue = [YTPlayerView stringForPlaybackQuality:suggestedQuality]; 761 | NSString *command = [NSString stringWithFormat:@"player.loadPlaylist(%@, %@, %@, '%@');", 762 | cueingString, indexValue, startSecondsValue, qualityValue]; 763 | [self stringFromEvaluatingJavaScript:command]; 764 | } 765 | 766 | /** 767 | * Private helper method for converting an NSArray of video IDs into its JavaScript equivalent. 768 | * 769 | * @param videoIds An array of video ID strings to convert into JavaScript format. 770 | * @return A JavaScript array in String format containing video IDs. 771 | */ 772 | - (NSString *)stringFromVideoIdArray:(NSArray *)videoIds { 773 | NSMutableArray *formattedVideoIds = [[NSMutableArray alloc] init]; 774 | 775 | for (id unformattedId in videoIds) { 776 | [formattedVideoIds addObject:[NSString stringWithFormat:@"'%@'", unformattedId]]; 777 | } 778 | 779 | return [NSString stringWithFormat:@"[%@]", [formattedVideoIds componentsJoinedByString:@", "]]; 780 | } 781 | 782 | /** 783 | * Private method for evaluating JavaScript in the WebView. 784 | * 785 | * @param jsToExecute The JavaScript code in string format that we want to execute. 786 | * @return JavaScript response from evaluating code. 787 | */ 788 | - (NSString *)stringFromEvaluatingJavaScript:(NSString *)jsToExecute { 789 | return [self.webView stringByEvaluatingJavaScriptFromString:jsToExecute]; 790 | } 791 | 792 | /** 793 | * Private method to convert a Objective-C BOOL value to JS boolean value. 794 | * 795 | * @param boolValue Objective-C BOOL value. 796 | * @return JavaScript Boolean value, i.e. "true" or "false". 797 | */ 798 | - (NSString *)stringForJSBoolean:(BOOL)boolValue { 799 | return boolValue ? @"true" : @"false"; 800 | } 801 | 802 | #pragma mark Exposed for Testing 803 | - (void)setWebView:(UIWebView *)webView { 804 | _webView = webView; 805 | } 806 | 807 | - (UIWebView *)createNewWebView { 808 | UIWebView *webView = [[UIWebView alloc] initWithFrame:self.bounds]; 809 | webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); 810 | webView.scrollView.scrollEnabled = NO; 811 | webView.scrollView.bounces = NO; 812 | return webView; 813 | } 814 | 815 | - (void)removeWebView { 816 | [self.webView removeFromSuperview]; 817 | self.webView = nil; 818 | } 819 | 820 | @end 821 | --------------------------------------------------------------------------------