forked from mattgallagher/AudioStreamer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MacStreamingPlayerController.m
263 lines (240 loc) · 5.92 KB
/
MacStreamingPlayerController.m
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
102
103
104
105
106
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//
// MacStreamingPlayerController.m
// MacStreamingPlayer
//
// Created by Matt Gallagher on 28/10/08.
// Copyright Matt Gallagher 2008. All rights reserved.
//
// Permission is given to use this source code file, free of charge, in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import "MacStreamingPlayerController.h"
#import "AudioStreamer.h"
#import <QuartzCore/CoreAnimation.h>
@implementation MacStreamingPlayerController
//
// setButtonImage:
//
// Used to change the image on the playbutton. This method exists for
// the purpose of inter-thread invocation because
// the observeValueForKeyPath:ofObject:change:context: method is invoked
// from secondary threads and UI updates are only permitted on the main thread.
//
// Parameters:
// image - the image to set on the play button.
//
- (void)setButtonImage:(NSImage *)image
{
[button.layer removeAllAnimations];
if (!image)
{
[button setImage:[NSImage imageNamed:@"playbutton"]];
}
else
{
[button setImage:image];
if ([button.image isEqual:[NSImage imageNamed:@"loadingbutton"]])
{
[self spinButton];
}
}
}
//
// destroyStreamer
//
// Removes the streamer, the UI update timer and the change notification
//
- (void)destroyStreamer
{
if (streamer)
{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:ASStatusChangedNotification
object:streamer];
[progressUpdateTimer invalidate];
progressUpdateTimer = nil;
[streamer stop];
[streamer release];
streamer = nil;
}
}
//
// createStreamer
//
// Creates or recreates the AudioStreamer object.
//
- (void)createStreamer
{
if (streamer)
{
return;
}
[self destroyStreamer];
NSString *escapedValue =
[(NSString *)CFURLCreateStringByAddingPercentEscapes(
nil,
(CFStringRef)[downloadSourceField stringValue],
NULL,
NULL,
kCFStringEncodingUTF8)
autorelease];
NSURL *url = [NSURL URLWithString:escapedValue];
streamer = [[AudioStreamer alloc] initWithURL:url];
progressUpdateTimer =
[NSTimer
scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(updateProgress:)
userInfo:nil
repeats:YES];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playbackStateChanged:)
name:ASStatusChangedNotification
object:streamer];
}
//
// spinButton
//
// Shows the spin button when the audio is loading. This is largely irrelevant
// now that the audio is loaded from a local file.
//
- (void)spinButton
{
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
CGRect frame = NSRectToCGRect([button frame]);
button.layer.anchorPoint = CGPointMake(0.5, 0.5);
button.layer.position = CGPointMake(frame.origin.x + 0.5 * frame.size.width, frame.origin.y + 0.5 * frame.size.height);
[CATransaction commit];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanFalse forKey:kCATransactionDisableActions];
[CATransaction setValue:[NSNumber numberWithFloat:2.0] forKey:kCATransactionAnimationDuration];
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0];
animation.toValue = [NSNumber numberWithFloat:-2 * M_PI];
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionLinear];
animation.delegate = self;
[button.layer addAnimation:animation forKey:@"rotationAnimation"];
[CATransaction commit];
}
//
// animationDidStop:finished:
//
// Restarts the spin animation on the button when it ends. Again, this is
// largely irrelevant now that the audio is loaded from a local file.
//
// Parameters:
// theAnimation - the animation that rotated the button.
// finished - is the animation finised?
//
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)finished
{
if (finished)
{
[self spinButton];
}
}
//
// buttonPressed:
//
// Handles the play/stop button. Creates, observes and starts the
// audio streamer when it is a play button. Stops the audio streamer when
// it isn't.
//
// Parameters:
// sender - normally, the play/stop button.
//
- (IBAction)buttonPressed:(id)sender
{
if ([button.image isEqual:[NSImage imageNamed:@"playbutton"]])
{
[downloadSourceField resignFirstResponder];
[self createStreamer];
[self setButtonImage:[NSImage imageNamed:@"loadingbutton"]];
[streamer start];
}
else
{
[streamer stop];
}
}
//
// playbackStateChanged:
//
// Invoked when the AudioStreamer
// reports that its playback status has changed.
//
- (void)playbackStateChanged:(NSNotification *)aNotification
{
if ([streamer isWaiting])
{
[self setButtonImage:[NSImage imageNamed:@"loadingbutton"]];
}
else if ([streamer isPlaying])
{
[self setButtonImage:[NSImage imageNamed:@"stopbutton"]];
}
else if ([streamer isIdle])
{
[self destroyStreamer];
[self setButtonImage:[NSImage imageNamed:@"playbutton"]];
}
}
//
// updateProgress:
//
// Invoked when the AudioStreamer
// reports that its playback progress has changed.
//
- (void)updateProgress:(NSTimer *)updatedTimer
{
if (streamer.bitRate != 0.0)
{
double progress = streamer.progress;
[positionLabel setStringValue:
[NSString stringWithFormat:@"Time Played: %.1f seconds",
progress]];
}
else
{
[positionLabel setStringValue:@"Time Played:"];
}
}
//
// textFieldShouldReturn:
//
// Dismiss the text field when done is pressed
//
// Parameters:
// sender - the text field
//
// returns YES
//
- (BOOL)textFieldShouldReturn:(NSTextField *)sender
{
[sender resignFirstResponder];
[self createStreamer];
return YES;
}
//
// dealloc
//
// Releases instance memory.
//
- (void)dealloc
{
[self destroyStreamer];
if (progressUpdateTimer)
{
[progressUpdateTimer invalidate];
progressUpdateTimer = nil;
}
[super dealloc];
}
@end