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