PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/castvideos-ios/finish/Classes/CastComponents/CastViewController.m

https://gitlab.com/adam.lukaitis/io2015-codelabs
Objective C | 648 lines | 484 code | 92 blank | 72 comment | 53 complexity | ff61c0d79347b05174d8de439cc12118 MD5 | raw file
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "AppDelegate.h"
  15. #import "CastViewController.h"
  16. #import "ChromecastDeviceController.h"
  17. #import "SimpleImageFetcher.h"
  18. #import "TracksTableViewController.h"
  19. #import <GoogleCast/GCKDevice.h>
  20. #import <GoogleCast/GCKMediaControlChannel.h>
  21. #import <GoogleCast/GCKMediaInformation.h>
  22. #import <GoogleCast/GCKMediaMetadata.h>
  23. #import <GoogleCast/GCKMediaStatus.h>
  24. static NSString * const kListTracks = @"listTracks";
  25. static NSString * const kListTracksPopover = @"listTracksPopover";
  26. NSString * const kCastComponentPosterURL = @"castComponentPosterURL";
  27. @interface CastViewController () {
  28. NSTimeInterval _mediaStartTime;
  29. /* Flag to indicate we are scrubbing - the play position is only updated at the end. */
  30. BOOL _currentlyDraggingSlider;
  31. /* Flag to indicate whether we have status from the Cast device and can show the UI. */
  32. BOOL _readyToShowInterface;
  33. /* Flag for whether we are reconnecting or playing from scratch. */
  34. BOOL _joinExistingSession;
  35. /* The most recent playback time - used for syncing between local and remote playback. */
  36. NSTimeInterval _lastKnownTime;
  37. }
  38. /* The device manager used for the currently casting media. */
  39. @property(weak, nonatomic) ChromecastDeviceController *castDeviceController;
  40. /* The image of the current media. */
  41. @property IBOutlet UIImageView* thumbnailImage;
  42. /* The label displaying the currently connected device. */
  43. @property IBOutlet UILabel* castingToLabel;
  44. /* The label displaying the currently playing media. */
  45. @property(weak, nonatomic) IBOutlet UILabel* mediaTitleLabel;
  46. /* An activity indicator while the cast is starting. */
  47. @property(weak, nonatomic) IBOutlet UIActivityIndicatorView* castActivityIndicator;
  48. /* A timer to trigger a callback to update the times/slider position. */
  49. @property(weak, nonatomic) NSTimer* updateStreamTimer;
  50. /* A timer to trigger removal of the volume control. */
  51. @property(weak, nonatomic) NSTimer* fadeVolumeControlTimer;
  52. /* The time of the play head in the current video. */
  53. @property(nonatomic) UILabel* currTime;
  54. /* The total time of the video. */
  55. @property(nonatomic) UILabel* totalTime;
  56. /* The tracks selector button (for closed captions primarily in this sample). */
  57. @property(nonatomic) UIButton* cc;
  58. /* The button that brings up the volume control: Apple recommends not overriding the hardware
  59. volume controls, so we use a separate on-screen UI. */
  60. @property(nonatomic) UIButton* volumeButton;
  61. /* The play icon button. */
  62. @property(nonatomic) UIButton* playButton;
  63. /* A slider for the progress/scrub bar. */
  64. @property(nonatomic) UISlider* slider;
  65. /* A containing view for the toolbar. */
  66. @property(nonatomic) UIView *toolbarView;
  67. /* Views dictionary used for the visual format layout management. */
  68. @property(nonatomic) NSDictionary *viewsDictionary;
  69. /* Play image. */
  70. @property(nonatomic) UIImage *playImage;
  71. /* Pause image. */
  72. @property(nonatomic) UIImage *pauseImage;
  73. /* Whether the viewcontroller is currently visible. */
  74. @property BOOL visible;
  75. @end
  76. @implementation CastViewController
  77. - (void)viewDidLoad {
  78. [super viewDidLoad];
  79. self.visible = false;
  80. self.castDeviceController = [ChromecastDeviceController sharedInstance];
  81. self.castingToLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Casting to %@", nil),
  82. _castDeviceController.deviceManager.device.friendlyName];
  83. self.mediaTitleLabel.text = [self.mediaToPlay.metadata stringForKey:kGCKMetadataKeyTitle];
  84. self.volumeControlLabel.text = [NSString stringWithFormat:NSLocalizedString(@"%@ Volume", nil),
  85. _castDeviceController.deviceManager.device.friendlyName];
  86. self.volumeSlider.minimumValue = 0;
  87. self.volumeSlider.maximumValue = 1.0;
  88. self.volumeSlider.value = _castDeviceController.deviceManager.deviceVolume ?
  89. _castDeviceController.deviceManager.deviceVolume : 0.5;
  90. self.volumeSlider.continuous = NO;
  91. [self.volumeSlider addTarget:self
  92. action:@selector(sliderValueChanged:)
  93. forControlEvents:UIControlEventValueChanged];
  94. UIButton *transparencyButton = [[UIButton alloc] initWithFrame:self.view.bounds];
  95. transparencyButton.autoresizingMask =
  96. (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
  97. transparencyButton.backgroundColor = [UIColor clearColor];
  98. [self.view insertSubview:transparencyButton aboveSubview:self.thumbnailImage];
  99. [transparencyButton addTarget:self
  100. action:@selector(showVolumeSlider:)
  101. forControlEvents:UIControlEventTouchUpInside];
  102. [self initControls];
  103. }
  104. - (void)viewWillAppear:(BOOL)animated {
  105. [super viewWillAppear:animated];
  106. // Listen for volume change notifications.
  107. [[NSNotificationCenter defaultCenter] addObserver:self
  108. selector:@selector(volumeDidChange)
  109. name:@"castVolumeChanged"
  110. object:nil];
  111. [[NSNotificationCenter defaultCenter] addObserver:self
  112. selector:@selector(didReceiveMediaStateChange)
  113. name:@"castMediaStatusChange"
  114. object:nil];
  115. // Add the cast icon to our nav bar.
  116. [[ChromecastDeviceController sharedInstance] decorateViewController:self];
  117. // Make the navigation bar transparent.
  118. self.navigationController.navigationBar.translucent = YES;
  119. [self.navigationController.navigationBar setBackgroundImage:[UIImage new]
  120. forBarMetrics:UIBarMetricsDefault];
  121. self.navigationController.navigationBar.shadowImage = [UIImage new];
  122. self.toolbarView.hidden = YES;
  123. [self.playButton setImage:self.playImage forState:UIControlStateNormal];
  124. [self resetInterfaceElements];
  125. if (_joinExistingSession == YES) {
  126. [self mediaNowPlaying];
  127. }
  128. [self configureView];
  129. }
  130. - (void)viewDidAppear:(BOOL)animated {
  131. [super viewDidAppear:animated];
  132. self.visible = true;
  133. if (_castDeviceController.deviceManager.applicationConnectionState
  134. != GCKConnectionStateConnected) {
  135. // If we're not connected, exit.
  136. [self maybePopController];
  137. }
  138. }
  139. - (void)viewWillDisappear:(BOOL)animated {
  140. // I think we can safely stop the timer here
  141. [self.updateStreamTimer invalidate];
  142. self.updateStreamTimer = nil;
  143. // We no longer want to be delegate.
  144. [[NSNotificationCenter defaultCenter] removeObserver:self];
  145. [self.navigationController.navigationBar setBackgroundImage:nil
  146. forBarMetrics:UIBarMetricsDefault];
  147. }
  148. - (void)viewDidDisappear:(BOOL)animated {
  149. self.visible = false;
  150. [super viewDidDisappear:animated];
  151. }
  152. - (IBAction)sliderValueChanged:(id)sender {
  153. UISlider *slider = (UISlider *) sender;
  154. NSLog(@"Got new slider value: %.2f", slider.value);
  155. [_castDeviceController.deviceManager setVolume:slider.value];
  156. }
  157. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  158. if (!_castDeviceController) {
  159. self.castDeviceController = [ChromecastDeviceController sharedInstance];
  160. }
  161. if ([segue.identifier isEqualToString:kListTracks] ||
  162. [segue.identifier isEqualToString:kListTracksPopover]) {
  163. UITabBarController *controller;
  164. controller = (UITabBarController *)[(UINavigationController *)[segue destinationViewController] visibleViewController];
  165. TracksTableViewController *trackController = controller.viewControllers[0];
  166. [trackController setMedia:self.mediaToPlay
  167. forType:GCKMediaTrackTypeText
  168. deviceController:_castDeviceController.mediaControlChannel];
  169. TracksTableViewController *audioTrackController = controller.viewControllers[1];
  170. [audioTrackController setMedia:self.mediaToPlay
  171. forType:GCKMediaTrackTypeAudio
  172. deviceController:_castDeviceController.mediaControlChannel];
  173. }
  174. }
  175. - (IBAction)unwindToCastView:(UIStoryboardSegue *)segue; {
  176. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  177. [self dismissViewControllerAnimated:YES completion:nil];
  178. }
  179. }
  180. - (void)maybePopController {
  181. // Only take action if we're visible.
  182. if (self.visible) {
  183. self.mediaToPlay = nil; // Forget media.
  184. [self.navigationController popViewControllerAnimated:YES];
  185. }
  186. }
  187. #pragma mark - Managing the detail item
  188. - (void)setMediaToPlay:(GCKMediaInformation *)newDetailItem {
  189. [self setMediaToPlay:newDetailItem withStartingTime:0];
  190. }
  191. - (void)setMediaToPlay:(GCKMediaInformation *)newMedia withStartingTime:(NSTimeInterval)startTime {
  192. _mediaStartTime = startTime;
  193. if (_mediaToPlay != newMedia) {
  194. _mediaToPlay = newMedia;
  195. }
  196. }
  197. - (void)resetInterfaceElements {
  198. self.totalTime.text = @"";
  199. self.currTime.text = @"";
  200. [self.slider setValue:0];
  201. [self.castActivityIndicator startAnimating];
  202. _currentlyDraggingSlider = NO;
  203. self.toolbarView.hidden = YES;
  204. _readyToShowInterface = NO;
  205. }
  206. - (IBAction)showVolumeSlider:(id)sender {
  207. if(self.volumeControls.hidden) {
  208. self.volumeControls.hidden = NO;
  209. [self.volumeControls setAlpha:0];
  210. [UIView animateWithDuration:0.5
  211. animations:^{
  212. self.volumeControls.alpha = 1.0;
  213. }
  214. completion:^(BOOL finished){
  215. NSLog(@"Done!");
  216. }];
  217. }
  218. // Do this so if a user taps the screen or plays with the volume slider, it resets the timer
  219. // for fading the volume controls
  220. if(self.fadeVolumeControlTimer != nil) {
  221. [self.fadeVolumeControlTimer invalidate];
  222. }
  223. self.fadeVolumeControlTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
  224. target:self
  225. selector:@selector(fadeVolumeSlider:)
  226. userInfo:nil repeats:NO];
  227. }
  228. - (void)fadeVolumeSlider:(NSTimer *)timer {
  229. [self.volumeControls setAlpha:1.0];
  230. [UIView animateWithDuration:0.5
  231. animations:^{
  232. self.volumeControls.alpha = 0.0;
  233. }
  234. completion:^(BOOL finished){
  235. self.volumeControls.hidden = YES;
  236. }];
  237. }
  238. - (void)mediaNowPlaying {
  239. _readyToShowInterface = YES;
  240. [self updateInterfaceFromCast:nil];
  241. self.toolbarView.hidden = NO;
  242. }
  243. - (void)updateInterfaceFromCast:(NSTimer*)timer {
  244. if (!_readyToShowInterface)
  245. return;
  246. if (_castDeviceController.playerState != GCKMediaPlayerStateBuffering) {
  247. [self.castActivityIndicator stopAnimating];
  248. } else {
  249. [self.castActivityIndicator startAnimating];
  250. }
  251. if (_castDeviceController.streamDuration > 0 && !_currentlyDraggingSlider) {
  252. _lastKnownTime = _castDeviceController.streamPosition;
  253. self.currTime.text = [self getFormattedTime:_castDeviceController.streamPosition];
  254. self.totalTime.text = [self getFormattedTime:_castDeviceController.streamDuration];
  255. [self.slider
  256. setValue:(_castDeviceController.streamPosition / _castDeviceController.streamDuration)
  257. animated:YES];
  258. }
  259. [self updateToolbarControls];
  260. }
  261. - (void)updateToolbarControls {
  262. if (_castDeviceController.playerState == GCKMediaPlayerStatePaused ||
  263. _castDeviceController.playerState == GCKMediaPlayerStateIdle) {
  264. [self.playButton setImage:self.playImage forState:UIControlStateNormal];
  265. } else if (_castDeviceController.playerState == GCKMediaPlayerStatePlaying ||
  266. _castDeviceController.playerState == GCKMediaPlayerStateBuffering) {
  267. [self.playButton setImage:self.pauseImage forState:UIControlStateNormal];
  268. }
  269. }
  270. // Little formatting option here
  271. - (NSString*)getFormattedTime:(NSTimeInterval)timeInSeconds {
  272. int seconds = round(timeInSeconds);
  273. int hours = seconds / (60 * 60);
  274. seconds %= (60 * 60);
  275. int minutes = seconds / 60;
  276. seconds %= 60;
  277. if (hours > 0) {
  278. return [NSString stringWithFormat:@"%d:%02d:%02d", hours, minutes, seconds];
  279. } else {
  280. return [NSString stringWithFormat:@"%d:%02d", minutes, seconds];
  281. }
  282. }
  283. - (void)configureView {
  284. if (self.mediaToPlay && _castDeviceController.deviceManager.applicationConnectionState
  285. == GCKConnectionStateConnected) {
  286. NSURL* url = self.mediaToPlay.customData;
  287. NSString *title = [_mediaToPlay.metadata stringForKey:kGCKMetadataKeyTitle];
  288. self.castingToLabel.text =
  289. [NSString stringWithFormat:@"Casting to %@",
  290. _castDeviceController.deviceManager.device.friendlyName];
  291. self.mediaTitleLabel.text = title;
  292. NSLog(@"Casting movie %@ at starting time %f", url, _mediaStartTime);
  293. //Loading thumbnail async
  294. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  295. NSString *posterURL = [_mediaToPlay.metadata stringForKey:kCastComponentPosterURL];
  296. if (posterURL) {
  297. UIImage* image = [UIImage
  298. imageWithData:[SimpleImageFetcher getDataFromImageURL:[NSURL URLWithString:posterURL]]];
  299. dispatch_async(dispatch_get_main_queue(), ^{
  300. NSLog(@"Loaded thumbnail image");
  301. self.thumbnailImage.image = image;
  302. [self.view setNeedsLayout];
  303. });
  304. }
  305. });
  306. self.cc.enabled = [self.mediaToPlay.mediaTracks count] > 0;
  307. NSString *cur = [_castDeviceController.mediaInformation.metadata
  308. stringForKey:kGCKMetadataKeyTitle];
  309. // If the newMedia is already playing, join the existing session.
  310. if (![title isEqualToString:cur] ||
  311. _castDeviceController.playerState == GCKMediaPlayerStateIdle) {
  312. //Cast the movie!
  313. [_castDeviceController loadMedia:self.mediaToPlay
  314. startTime:_mediaStartTime
  315. autoPlay:YES];
  316. _joinExistingSession = NO;
  317. } else {
  318. _joinExistingSession = YES;
  319. [self mediaNowPlaying];
  320. }
  321. // Start the timer
  322. if (self.updateStreamTimer) {
  323. [self.updateStreamTimer invalidate];
  324. self.updateStreamTimer = nil;
  325. }
  326. self.updateStreamTimer =
  327. [NSTimer scheduledTimerWithTimeInterval:1.0
  328. target:self
  329. selector:@selector(updateInterfaceFromCast:)
  330. userInfo:nil
  331. repeats:YES];
  332. }
  333. }
  334. #pragma mark - On - screen UI elements
  335. - (IBAction)playButtonClicked:(id)sender {
  336. if (_castDeviceController.playerState == GCKMediaPlayerStatePaused) {
  337. [_castDeviceController.mediaControlChannel play];
  338. } else {
  339. [_castDeviceController.mediaControlChannel pause];
  340. }
  341. }
  342. - (IBAction)subtitleButtonClicked:(id)sender {
  343. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
  344. [self performSegueWithIdentifier:kListTracksPopover sender:self];
  345. } else {
  346. [self performSegueWithIdentifier:kListTracks sender:self];
  347. }
  348. }
  349. - (IBAction)onTouchDown:(id)sender {
  350. _currentlyDraggingSlider = YES;
  351. }
  352. // This is continuous, so we can update the current/end time labels
  353. - (IBAction)onSliderValueChanged:(id)sender {
  354. float pctThrough = [self.slider value];
  355. if (_castDeviceController.streamDuration > 0) {
  356. self.currTime.text =
  357. [self getFormattedTime:(pctThrough * _castDeviceController.streamDuration)];
  358. }
  359. }
  360. // This is called only on one of the two touch up events
  361. - (void)touchIsFinished {
  362. [_castDeviceController setPlaybackPercent:[self.slider value]];
  363. _currentlyDraggingSlider = NO;
  364. }
  365. - (IBAction)onTouchUpInside:(id)sender {
  366. NSLog(@"Touch up inside");
  367. [self touchIsFinished];
  368. }
  369. - (IBAction)onTouchUpOutside:(id)sender {
  370. NSLog(@"Touch up outside");
  371. [self touchIsFinished];
  372. }
  373. #pragma mark - ChromecastControllerDelegate
  374. /**
  375. * Called when connection to the device was closed.
  376. */
  377. - (void)didDisconnect {
  378. [self maybePopController];
  379. }
  380. /**
  381. * Called when the playback state of media on the device changes.
  382. */
  383. - (void)didReceiveMediaStateChange {
  384. NSString *currentlyPlayingMediaTitle = [_castDeviceController.mediaInformation.metadata
  385. stringForKey:kGCKMetadataKeyTitle];
  386. NSString *title = [_mediaToPlay.metadata stringForKey:kGCKMetadataKeyTitle];
  387. if (currentlyPlayingMediaTitle &&
  388. ![title isEqualToString:currentlyPlayingMediaTitle]) {
  389. // The state change is related to old media, so ignore it.
  390. NSLog(@"Got message for media %@ while on %@", currentlyPlayingMediaTitle, title);
  391. return;
  392. }
  393. if (_castDeviceController.playerState == GCKMediaPlayerStateIdle && _mediaToPlay) {
  394. [self maybePopController];
  395. return;
  396. }
  397. _readyToShowInterface = YES;
  398. if ([self isViewLoaded] && self.view.window) {
  399. // Display toolbar if we are current view.
  400. self.toolbarView.hidden = NO;
  401. }
  402. }
  403. #pragma mark - implementation.
  404. - (void)initControls {
  405. // Play/Pause images.
  406. self.playImage = [UIImage imageNamed:@"media_play"];
  407. self.pauseImage = [UIImage imageNamed:@"media_pause"];
  408. // Toolbar.
  409. self.toolbarView = [[UIView alloc] initWithFrame:self.navigationController.toolbar.frame];
  410. self.toolbarView.translatesAutoresizingMaskIntoConstraints = NO;
  411. // Hide the nav controller toolbar - we are managing our own to get autolayout.
  412. self.navigationController.toolbarHidden = YES;
  413. // Play/Pause button.
  414. self.playButton = [UIButton buttonWithType:UIButtonTypeSystem];
  415. [self.playButton setFrame:CGRectMake(0, 0, 40, 40)];
  416. if (_castDeviceController.playerState == GCKMediaPlayerStatePaused) {
  417. [self.playButton setImage:self.playImage forState:UIControlStateNormal];
  418. } else {
  419. [self.playButton setImage:self.pauseImage forState:UIControlStateNormal];
  420. }
  421. [self.playButton addTarget:self
  422. action:@selector(playButtonClicked:)
  423. forControlEvents:UIControlEventTouchUpInside];
  424. self.playButton.tintColor = [UIColor whiteColor];
  425. NSLayoutConstraint *constraint =[NSLayoutConstraint
  426. constraintWithItem:self.playButton
  427. attribute:NSLayoutAttributeHeight
  428. relatedBy:NSLayoutRelationEqual
  429. toItem:self.playButton
  430. attribute:NSLayoutAttributeWidth
  431. multiplier:1.0
  432. constant:0.0f];
  433. [self.playButton addConstraint:constraint];
  434. self.playButton.translatesAutoresizingMaskIntoConstraints = NO;
  435. // Current time.
  436. self.currTime = [[UILabel alloc] init];
  437. self.currTime.clearsContextBeforeDrawing = YES;
  438. self.currTime.text = @"00:00";
  439. [self.currTime setFont:[UIFont fontWithName:@"Helvetica" size:14.0]];
  440. [self.currTime setTextColor:[UIColor whiteColor]];
  441. self.currTime.tintColor = [UIColor whiteColor];
  442. self.currTime.translatesAutoresizingMaskIntoConstraints = NO;
  443. // Total time.
  444. self.totalTime = [[UILabel alloc] init];
  445. self.totalTime.clearsContextBeforeDrawing = YES;
  446. self.totalTime.text = @"00:00";
  447. [self.totalTime setFont:[UIFont fontWithName:@"Helvetica" size:14.0]];
  448. [self.totalTime setTextColor:[UIColor whiteColor]];
  449. self.totalTime.tintColor = [UIColor whiteColor];
  450. self.totalTime.translatesAutoresizingMaskIntoConstraints = NO;
  451. // Volume control.
  452. self.volumeButton = [UIButton buttonWithType:UIButtonTypeSystem];
  453. [self.volumeButton setFrame:CGRectMake(0, 0, 40, 40)];
  454. [self.volumeButton setImage:[UIImage imageNamed:@"icon_volume3"] forState:UIControlStateNormal];
  455. [self.volumeButton addTarget:self
  456. action:@selector(showVolumeSlider:)
  457. forControlEvents:UIControlEventTouchUpInside];
  458. self.volumeButton.tintColor = [UIColor whiteColor];
  459. constraint =[NSLayoutConstraint
  460. constraintWithItem:self.volumeButton
  461. attribute:NSLayoutAttributeHeight
  462. relatedBy:NSLayoutRelationEqual
  463. toItem:self.volumeButton
  464. attribute:NSLayoutAttributeWidth
  465. multiplier:1.0
  466. constant:0.0f];
  467. [self.volumeButton addConstraint:constraint];
  468. self.volumeButton.translatesAutoresizingMaskIntoConstraints = NO;
  469. // Tracks selector.
  470. self.cc = [UIButton buttonWithType:UIButtonTypeSystem];
  471. [self.cc setFrame:CGRectMake(0, 0, 40, 40)];
  472. [self.cc setImage:[UIImage imageNamed:@"closed_caption_white.png.png"] forState:UIControlStateNormal];
  473. [self.cc addTarget:self
  474. action:@selector(subtitleButtonClicked:)
  475. forControlEvents:UIControlEventTouchUpInside];
  476. self.cc.tintColor = [UIColor whiteColor];
  477. constraint =[NSLayoutConstraint
  478. constraintWithItem:self.cc
  479. attribute:NSLayoutAttributeHeight
  480. relatedBy:NSLayoutRelationEqual
  481. toItem:self.cc
  482. attribute:NSLayoutAttributeWidth
  483. multiplier:1.0
  484. constant:0.0f];
  485. [self.cc addConstraint:constraint];
  486. self.cc.translatesAutoresizingMaskIntoConstraints = NO;
  487. // Slider.
  488. self.slider = [[UISlider alloc] init];
  489. UIImage *thumb = [UIImage imageNamed:@"thumb.png"];
  490. [self.slider setThumbImage:thumb forState:UIControlStateNormal];
  491. [self.slider setThumbImage:thumb forState:UIControlStateHighlighted];
  492. [self.slider addTarget:self
  493. action:@selector(onSliderValueChanged:)
  494. forControlEvents:UIControlEventValueChanged];
  495. [self.slider addTarget:self
  496. action:@selector(onTouchDown:)
  497. forControlEvents:UIControlEventTouchDown];
  498. [self.slider addTarget:self
  499. action:@selector(onTouchUpInside:)
  500. forControlEvents:UIControlEventTouchUpInside];
  501. [self.slider addTarget:self
  502. action:@selector(onTouchUpOutside:)
  503. forControlEvents:UIControlEventTouchCancel];
  504. [self.slider addTarget:self
  505. action:@selector(onTouchUpOutside:)
  506. forControlEvents:UIControlEventTouchUpOutside];
  507. self.slider.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  508. self.slider.minimumValue = 0;
  509. self.slider.minimumTrackTintColor = [UIColor yellowColor];
  510. self.slider.translatesAutoresizingMaskIntoConstraints = NO;
  511. [self.view addSubview:self.toolbarView];
  512. [self.toolbarView addSubview:self.playButton];
  513. [self.toolbarView addSubview:self.volumeButton];
  514. [self.toolbarView addSubview:self.currTime];
  515. [self.toolbarView addSubview:self.slider];
  516. [self.toolbarView addSubview:self.totalTime];
  517. [self.toolbarView addSubview:self.cc];
  518. // Round the corners on the volume pop up.
  519. self.volumeControls.layer.cornerRadius = 5;
  520. self.volumeControls.layer.masksToBounds = YES;
  521. // Layout.
  522. NSString *hlayout =
  523. @"|-(<=5)-[playButton(==35)]-[volumeButton(==30)]-[currTime]-[slider(>=90)]-[totalTime]-[ccButton(==playButton)]-(<=5)-|";
  524. self.viewsDictionary = @{ @"slider" : self.slider,
  525. @"currTime" : self.currTime,
  526. @"totalTime" : self.totalTime,
  527. @"playButton" : self.playButton,
  528. @"volumeButton" : self.volumeButton,
  529. @"ccButton" : self.cc
  530. };
  531. [self.toolbarView addConstraints:
  532. [NSLayoutConstraint constraintsWithVisualFormat:hlayout
  533. options:NSLayoutFormatAlignAllCenterY
  534. metrics:nil views:self.viewsDictionary]];
  535. NSString *vlayout = @"V:[slider(==35)]-|";
  536. [self.toolbarView addConstraints:
  537. [NSLayoutConstraint constraintsWithVisualFormat:vlayout
  538. options:0
  539. metrics:nil views:self.viewsDictionary]];
  540. // Autolayout toolbar.
  541. NSString *toolbarVLayout = @"V:[toolbar(==44)]|";
  542. NSString *toolbarHLayout = @"|[toolbar]|";
  543. [self.view addConstraints:
  544. [NSLayoutConstraint constraintsWithVisualFormat:toolbarVLayout
  545. options:0
  546. metrics:nil views:@{@"toolbar" : self.toolbarView}]];
  547. [self.view addConstraints:
  548. [NSLayoutConstraint constraintsWithVisualFormat:toolbarHLayout
  549. options:0
  550. metrics:nil views:@{@"toolbar" : self.toolbarView}]];
  551. }
  552. #pragma mark Volume listener.
  553. - (void)volumeDidChange {
  554. self.volumeSlider.value = _castDeviceController.deviceManager.deviceVolume;
  555. }
  556. @end