1/* 2 File: MovieControllerLayer.m 3 4 Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 5 Inc. ("Apple") in consideration of your agreement to the following 6 terms, and your use, installation, modification or redistribution of 7 this Apple software constitutes acceptance of these terms. If you do 8 not agree with these terms, please do not use, install, modify or 9 redistribute this Apple software. 10 11 In consideration of your agreement to abide by the following terms, and 12 subject to these terms, Apple grants you a personal, non-exclusive 13 license, under Apple's copyrights in this original Apple software (the 14 "Apple Software"), to use, reproduce, modify and redistribute the Apple 15 Software, with or without modifications, in source and/or binary forms; 16 provided that if you redistribute the Apple Software in its entirety and 17 without modifications, you must retain this notice and the following 18 text and disclaimers in all such redistributions of the Apple Software. 19 Neither the name, trademarks, service marks or logos of Apple Inc. may 20 be used to endorse or promote products derived from the Apple Software 21 without specific prior written permission from Apple. Except as 22 expressly stated in this notice, no other rights or licenses, express or 23 implied, are granted by Apple herein, including but not limited to any 24 patent rights that may be infringed by your derivative works or by other 25 works in which the Apple Software may be incorporated. 26 27 The Apple Software is provided by Apple on an "AS IS" basis. APPLE 28 MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 29 THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 30 FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 31 OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 32 33 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 34 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 35 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 37 MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 38 AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 39 STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 40 POSSIBILITY OF SUCH DAMAGE. 41 42 Copyright (C) 2009 Apple Inc. All Rights Reserved. 43 44 */ 45 46#import "MovieControllerLayer.h" 47 48#import <QTKit/QTKit.h> 49 50@interface MovieControllerLayer () 51- (BOOL)_isPlaying; 52- (NSTimeInterval)_currentTime; 53- (NSTimeInterval)_duration; 54@end 55 56@implementation MovieControllerLayer 57 58static CGImageRef createImageNamed(NSString *name) 59{ 60 NSURL *url = [[NSBundle bundleForClass:[MovieControllerLayer class]] URLForResource:name withExtension:@"tiff"]; 61 62 if (!url) 63 return NULL; 64 65 CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)url, NULL); 66 if (!imageSource) 67 return NULL; 68 69 CGImageRef image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); 70 CFRelease(imageSource); 71 72 return image; 73} 74 75- (id)init 76{ 77 if (self = [super init]) { 78 self.needsDisplayOnBoundsChange = YES; 79 self.frame = CGRectMake(0, 0, 0, 25); 80 self.autoresizingMask = kCALayerWidthSizable; 81 82 _playImage = createImageNamed(@"Play"); 83 _pauseImage = createImageNamed(@"Pause"); 84 _sliderTrackLeft = createImageNamed(@"SliderTrackLeft"); 85 _sliderTrackRight = createImageNamed(@"SliderTrackRight"); 86 _sliderTrackCenter = createImageNamed(@"SliderTrackCenter"); 87 88 _thumb = createImageNamed(@"Thumb"); 89 } 90 91 return self; 92} 93 94- (void)dealloc 95{ 96 CGImageRelease(_playImage); 97 CGImageRelease(_pauseImage); 98 99 CGImageRelease(_sliderTrackLeft); 100 CGImageRelease(_sliderTrackRight); 101 CGImageRelease(_sliderTrackCenter); 102 103 CGImageRelease(_thumb); 104 105 [self setMovie:nil]; 106 [_updateTimeTimer invalidate]; 107 108 [super dealloc]; 109} 110 111#pragma mark Drawing 112 113- (CGRect)_playPauseButtonRect 114{ 115 return CGRectMake(0, 0, 25, 25); 116} 117 118- (CGRect)_sliderRect 119{ 120 CGFloat sliderYPosition = (self.bounds.size.height - CGImageGetHeight(_sliderTrackLeft)) / 2.0; 121 CGFloat playPauseButtonWidth = [self _playPauseButtonRect].size.width; 122 123 return CGRectMake(playPauseButtonWidth, sliderYPosition, 124 self.bounds.size.width - playPauseButtonWidth - 7, CGImageGetHeight(_sliderTrackLeft)); 125} 126 127- (CGRect)_sliderThumbRect 128{ 129 CGRect sliderRect = [self _sliderRect]; 130 131 CGFloat fraction = 0.0; 132 if (_movie) 133 fraction = [self _currentTime] / [self _duration]; 134 135 CGFloat x = fraction * (CGRectGetWidth(sliderRect) - CGImageGetWidth(_thumb)); 136 137 return CGRectMake(CGRectGetMinX(sliderRect) + x, CGRectGetMinY(sliderRect) - 1, 138 CGImageGetWidth(_thumb), CGImageGetHeight(_thumb)); 139} 140 141- (CGRect)_innerSliderRect 142{ 143 return CGRectInset([self _sliderRect], CGRectGetWidth([self _sliderThumbRect]) / 2, 0); 144} 145 146- (void)_drawPlayPauseButtonInContext:(CGContextRef)context 147{ 148 CGContextDrawImage(context, [self _playPauseButtonRect], [self _isPlaying] ? _pauseImage : _playImage); 149} 150 151- (void)_drawSliderInContext:(CGContextRef)context 152{ 153 // Draw the thumb 154 CGRect sliderThumbRect = [self _sliderThumbRect]; 155 CGContextDrawImage(context, sliderThumbRect, _thumb); 156 157 CGRect sliderRect = [self _sliderRect]; 158 159 // Draw left part 160 CGRect sliderLeftTrackRect = CGRectMake(CGRectGetMinX(sliderRect), CGRectGetMinY(sliderRect), 161 CGImageGetWidth(_sliderTrackLeft), CGImageGetHeight(_sliderTrackLeft)); 162 CGContextDrawImage(context, sliderLeftTrackRect, _sliderTrackLeft); 163 164 // Draw center part 165 CGRect sliderCenterTrackRect = CGRectInset(sliderRect, CGImageGetWidth(_sliderTrackLeft), 0); 166 CGContextDrawImage(context, sliderCenterTrackRect, _sliderTrackCenter); 167 168 // Draw right part 169 CGRect sliderRightTrackRect = CGRectMake(CGRectGetMaxX(sliderCenterTrackRect), CGRectGetMinY(sliderRect), 170 CGImageGetWidth(_sliderTrackRight), CGImageGetHeight(_sliderTrackRight)); 171 CGContextDrawImage(context, sliderRightTrackRect, _sliderTrackRight); 172 173} 174 175- (void)drawInContext:(CGContextRef)context 176{ 177 CGContextSaveGState(context); 178 CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorBlack)); 179 CGContextFillRect(context, self.bounds); 180 CGContextRestoreGState(context); 181 182 [self _drawPlayPauseButtonInContext:context]; 183 [self _drawSliderInContext:context]; 184} 185 186#pragma mark Movie handling 187 188- (NSTimeInterval)_currentTime 189{ 190 if (!_movie) 191 return 0; 192 193 QTTime time = [_movie currentTime]; 194 NSTimeInterval timeInterval; 195 if (!QTGetTimeInterval(time, &timeInterval)) 196 return 0; 197 198 return timeInterval; 199} 200 201- (NSTimeInterval)_duration 202{ 203 if (!_movie) 204 return 0; 205 206 QTTime time = [_movie duration]; 207 NSTimeInterval timeInterval; 208 if (!QTGetTimeInterval(time, &timeInterval)) 209 return 0; 210 211 return timeInterval; 212} 213 214- (BOOL)_isPlaying 215{ 216 return [_movie rate] != 0.0; 217} 218 219- (void)_updateTime:(NSTimer *)timer 220{ 221 [self setNeedsDisplay]; 222} 223 224- (void)_rateDidChange:(NSNotification *)notification 225{ 226 float rate = [[[notification userInfo] objectForKey:QTMovieRateDidChangeNotificationParameter] floatValue]; 227 228 if (rate == 0.0) { 229 [_updateTimeTimer invalidate]; 230 _updateTimeTimer = nil; 231 } else 232 _updateTimeTimer = [NSTimer scheduledTimerWithTimeInterval:0.035 target:self selector:@selector(_updateTime:) userInfo:nil repeats:YES]; 233 234 [self setNeedsDisplay]; 235} 236 237- (void)_timeDidChange:(NSNotification *)notification 238{ 239 [self setNeedsDisplay]; 240} 241 242- (id<CAAction>)actionForKey:(NSString *)key 243{ 244 // We don't want to animate the contents of the layer. 245 if ([key isEqualToString:@"contents"]) 246 return nil; 247 248 return [super actionForKey:key]; 249} 250 251- (void)setMovie:(QTMovie *)movie 252{ 253 if (_movie == movie) 254 return; 255 256 if (_movie) { 257 [[NSNotificationCenter defaultCenter] removeObserver:self 258 name:QTMovieRateDidChangeNotification 259 object:_movie]; 260 [[NSNotificationCenter defaultCenter] removeObserver:self 261 name:QTMovieTimeDidChangeNotification 262 object:_movie]; 263 [_movie release]; 264 } 265 266 _movie = [movie retain]; 267 268 if (_movie) { 269 [[NSNotificationCenter defaultCenter] addObserver:self 270 selector:@selector(_rateDidChange:) 271 name:QTMovieRateDidChangeNotification 272 object:_movie]; 273 [[NSNotificationCenter defaultCenter] addObserver:self 274 selector:@selector(_timeDidChange:) 275 name:QTMovieTimeDidChangeNotification 276 object:_movie]; 277 [self setNeedsDisplay]; 278 } 279 280} 281 282# pragma mark Event handling 283 284- (void)_setNewTimeForThumbCenterX:(CGFloat)centerX 285{ 286 CGRect innerRect = [self _innerSliderRect]; 287 288 CGFloat fraction = (centerX - CGRectGetMinX(innerRect)) / CGRectGetWidth(innerRect); 289 if (fraction > 1.0) 290 fraction = 1.0; 291 else if (fraction < 0.0) 292 fraction = 0.0; 293 294 NSTimeInterval newTime = fraction * [self _duration]; 295 296 [_movie setCurrentTime:QTMakeTimeWithTimeInterval(newTime)]; 297 [self setNeedsDisplay]; 298} 299 300- (void)handleMouseDown:(CGPoint)point 301{ 302 if (!_movie) 303 return; 304 305 if (CGRectContainsPoint([self _sliderRect], point)) { 306 _wasPlayingBeforeMouseDown = [self _isPlaying]; 307 _isScrubbing = YES; 308 309 [_movie stop]; 310 if (CGRectContainsPoint([self _sliderThumbRect], point)) 311 _mouseDownXDelta = point.x - CGRectGetMidX([self _sliderThumbRect]); 312 else { 313 [self _setNewTimeForThumbCenterX:point.x]; 314 _mouseDownXDelta = 0; 315 } 316 } 317} 318 319- (void)handleMouseUp:(CGPoint)point 320{ 321 if (!_movie) 322 return; 323 324 if (_isScrubbing) { 325 _isScrubbing = NO; 326 _mouseDownXDelta = 0; 327 328 if (_wasPlayingBeforeMouseDown) 329 [_movie play]; 330 return; 331 } 332 333 if (CGRectContainsPoint([self _playPauseButtonRect], point)) { 334 if ([self _isPlaying]) 335 [_movie stop]; 336 else 337 [_movie play]; 338 return; 339 } 340} 341 342- (void)handleMouseDragged:(CGPoint)point 343{ 344 if (!_movie) 345 return; 346 347 if (!_isScrubbing) 348 return; 349 350 point.x -= _mouseDownXDelta; 351 352 [self _setNewTimeForThumbCenterX:point.x]; 353} 354 355@end 356