├── 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 |
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 |
--------------------------------------------------------------------------------