1/* vim: set ft=objc ts=4 nowrap: */ 2/* 3 * Player.m 4 * 5 * Copyright (c) 1999 - 2003 6 * 7 * Author: ACKyugo <ackyugo@geocities.co.jp> 8 * Andreas Schik <andreas@schik.de> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25 26#include "Player.h" 27#include "LED.h" 28#include "TrackList.h" 29 30static BOOL mustReadTOC = NO; 31static Player *sharedPlayer = nil; 32 33@implementation Player 34 35- init 36{ 37 if (sharedPlayer) { 38 [self dealloc]; 39 } else { 40 self = [super init]; 41 42 sharedPlayer = self; 43 44 track = 1; 45 drive = nil; 46 autoPlay = NO; 47 48 // we must already create the (hidden) track list 49 [TrackList sharedTrackList]; 50 51 if (![self loadAudioCDBundle]) { 52 [self release]; 53 return nil; 54 } 55 56 timer = [NSTimer scheduledTimerWithTimeInterval: 0.5 57 target: self 58 selector: @selector(timer:) 59 userInfo: self 60 repeats: YES]; 61 62 [[NSNotificationCenter defaultCenter] addObserver: self 63 selector: @selector(playTrack:) 64 name: @"PlayTrack" 65 object: nil]; 66 } 67 return sharedPlayer; 68} 69 70- (void) dealloc 71{ 72 [[NSNotificationCenter defaultCenter] removeObserver: self 73 name: @"PlayTrack" 74 object: nil]; 75 [drive release]; 76 [timer invalidate]; 77 [timer release]; 78 79 [led release]; 80 [window release]; 81 [prev release]; 82 [play release]; 83 [pause release]; 84 [stop release]; 85 [next release]; 86 87 [super dealloc]; 88} 89 90- (BOOL) loadAudioCDBundle 91{ 92 int i; 93 NSString *path; 94 NSArray *searchPaths; 95 96 // try to load the AudioCD bundle 97 searchPaths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, 98 NSUserDomainMask|NSLocalDomainMask|NSSystemDomainMask, YES); 99 100 for (i = 0; i < [searchPaths count]; i++) { 101 NSBundle *bundle; 102 NSString *bundlePath = [NSString stringWithFormat: 103 @"%@/Bundles/AudioCD.bundle", 104 [searchPaths objectAtIndex: i]]; 105 106 bundle = [NSBundle bundleWithPath: bundlePath]; 107 if (bundle) { 108 audiocdClass = [bundle principalClass]; 109 if (audiocdClass) { 110 break; 111 } 112 } 113 } // for (i = 0; i < [searchPaths count]; i++) 114 115 if (!audiocdClass) { 116 NSRunAlertPanel(@"CDPlayer", 117 _(@"Couldn't find AudioCD bundle."), 118 _(@"Exit"), nil, nil); 119 exit(-1); 120 } 121 122 path = [[NSUserDefaults standardUserDefaults] stringForKey: @"Device"]; 123 drive = [[audiocdClass alloc] initWithHandler: self]; 124 [drive startPollingWithPreferredDevice: path]; 125 126 return YES; 127} 128 129 130// 131// 132// 133- (void) timer: (id)timer 134{ 135 NSString *path; 136 int state; 137 138 path = [[NSUserDefaults standardUserDefaults] stringForKey: @"Device"]; 139 140 if (mustReadTOC) { 141 [[TrackList sharedTrackList] setTOC: [drive readTOC]]; 142 mustReadTOC = NO; 143 } 144 145 // is a disc in the drive? 146 if([drive cdPresent] == NO) { 147 [led setNoCD]; 148 [led display]; 149 return; 150 } 151 152 if (autoPlay) { 153 [drive playStart: 1 End: [drive totalTrack]]; 154 autoPlay = NO; 155 } 156 157 state = [drive currentState]; 158 159 if(state == AUDIOCD_PLAYING) { 160 [led setMin: [drive currentMin]]; 161 [led setSec: [drive currentSec]]; 162 track = [drive currentTrack]; 163 } else if(state == AUDIOCD_NOSTATUS) { 164 [led setMin: 0]; 165 [led setSec: 0]; 166 } 167 [led setTrack: track]; 168 [led display]; 169 [[TrackList sharedTrackList] setPlaysTrack: track]; 170} 171 172 173 174// 175// 176// 177 178- (void) playTrack: (NSNotification *)not 179{ 180 int nextTrack = [[[not userInfo] objectForKey: @"Track"] intValue]; 181 182 if (nextTrack == track) 183 return; 184 185 track = nextTrack; 186 [drive playStart: track End: [drive totalTrack]]; 187 [led setTrack: track]; 188 [led display]; 189 [[TrackList sharedTrackList] setPlaysTrack: track]; 190} 191 192 193- (void) play: (id)sender 194{ 195 if([drive cdPresent] == NO) { 196 return; 197 } 198 199 if ([drive currentState] == AUDIOCD_PAUSED) { 200 [drive resume]; 201 } else { 202 [drive playStart: track End: [drive totalTrack]]; 203 } 204} 205 206- (void) pause: (id)sender 207{ 208 if ([drive cdPresent] == NO) { 209 return; 210 } 211 212 if ([drive currentState] == AUDIOCD_PLAYING) { 213 [drive pause]; 214 } else if ([drive currentState] == AUDIOCD_PAUSED) { 215 [drive resume]; 216 } 217} 218 219- (void) stop: (id)sender 220{ 221 // the 'stop' button is also 'eject' if CD is already halted, 222 // but not the Controller, which may also stop the CD on exit 223 if(sender == stop) { 224 if ([drive cdPresent] == NO) { 225 [drive close]; 226 return; 227 } 228 229 if((sender == stop) && ([drive currentState] == AUDIOCD_NOSTATUS)) { 230 [drive eject]; 231 return; 232 } 233 } 234 235 [drive stop]; 236 track = 1; 237 [led setTrack: track]; 238 [led setMin: 0]; 239 [led setSec: 0]; 240 [led display]; 241 [[TrackList sharedTrackList] setPlaysTrack: 0]; 242} 243 244- (void) eject: (id)sender 245{ 246 if([drive cdPresent] == NO) { 247 return; 248 } 249 250 [drive eject]; 251} 252 253- (void) next: (id)sender 254{ 255 if([drive cdPresent] == NO) { 256 return; 257 } 258 259 track++; 260 if(track > [drive totalTrack]) { 261 track = 1; 262 } 263 264 if(([drive currentState] == AUDIOCD_PLAYING) || 265 ([drive currentState] == AUDIOCD_PAUSED)) { 266 [drive playStart: track End: [drive totalTrack]]; 267 } 268 [led setTrack: track]; 269 [led display]; 270 [[TrackList sharedTrackList] setPlaysTrack: track]; 271} 272 273- (void) prev: (id)sender 274{ 275 if([drive cdPresent] == NO) { 276 return; 277 } 278 279 // We jump back only if we are not playing at the moment 280 // or if we are at the very beginning of a playing track. 281 // The latter condition allows to jump back to the beginning 282 // of the current track before jumping back one more track. 283 if (([drive currentState] == AUDIOCD_NOSTATUS) || 284 ([drive currentSec] == 0 && [drive currentMin] == 0)) { 285 track--; 286 if(track < 1) { 287 track = [drive totalTrack]; 288 } 289 } 290 291 if(([drive currentState] == AUDIOCD_PLAYING) || 292 ([drive currentState] == AUDIOCD_PAUSED)) { 293 [drive playStart: track End: [drive totalTrack]]; 294 } 295 296 [led setTrack: track]; 297 [led display]; 298 [[TrackList sharedTrackList] setPlaysTrack: track]; 299} 300 301 302// 303// 304// 305// 306- (BOOL) audioCD: (id)sender error: (int)no message: (NSString *)msg 307{ 308 // FIXME: Take appropriate action when an error occurs!! 309// NSRunAlertPanel(@"CDPlayer", 310// msg, 311// _(@"OK"), nil, nil); 312 NSLog(@"CDPlayer error: %@", msg); 313 return YES; 314} 315 316- (void) audioCDChanged: (id)sender 317{ 318 mustReadTOC = YES; 319} 320 321 322- (BOOL) windowShouldClose: (id)sender 323{ 324 [[NSApplication sharedApplication ] terminate: self]; 325 return YES; 326} 327 328 329// 330// 331// 332- (void) buildInterface 333{ 334 NSRect frame; 335 unsigned int style = NSTitledWindowMask | NSClosableWindowMask | 336 NSMiniaturizableWindowMask; 337 NSBundle *bundle = [NSBundle mainBundle]; 338 NSImage *image; 339 NSString *path; 340 341 342 frame = NSMakeRect(100, 100, 160, 73); 343 window = [[NSWindow alloc] initWithContentRect: frame 344 styleMask: style 345 backing: NSBackingStoreRetained 346 defer: NO]; 347 [window setTitle: @"CDPlayer"]; 348 [window setDelegate: self]; 349 350 frame = NSMakeRect(5, 38, 150, 30); 351 led = [[LED alloc] initWithFrame: frame]; 352 353 [led setNoCD]; 354 [led display]; 355 [[window contentView] addSubview: led]; 356 357 path = [bundle pathForResource: @"prev" ofType: @"tiff"]; 358 image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease]; 359 if(image == nil) NSLog(@"cannot load prev.tiff"); 360 frame = NSMakeRect( 5, 5, 30, 30); 361 prev = [[NSButton alloc] initWithFrame: frame]; 362 [prev setButtonType: NSMomentaryPushButton]; 363 [prev setImagePosition: NSImageOnly]; 364 [prev setImage: image]; 365 [prev setTarget: self]; 366 [prev setAction: @selector(prev:)]; 367 [[window contentView] addSubview: prev]; 368 369 path = [bundle pathForResource: @"play" ofType: @"tiff"]; 370 image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease]; 371 if(image == nil) NSLog(@"cannot load play.tiff"); 372 frame = NSMakeRect( 35, 5, 30, 30); 373 play = [[NSButton alloc] initWithFrame: frame]; 374 [play setButtonType: NSMomentaryPushButton]; 375 [play setImagePosition: NSImageOnly]; 376 [play setImage: image]; 377 [play setTarget: self]; 378 [play setAction: @selector(play:)]; 379 [[window contentView] addSubview: play]; 380 381 path = [bundle pathForResource: @"pause" ofType: @"tiff"]; 382 image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease]; 383 if(image == nil) NSLog(@"cannot load pause.tiff"); 384 frame = NSMakeRect( 65, 5, 30, 30); 385 pause = [[NSButton alloc] initWithFrame: frame]; 386 [pause setButtonType: NSMomentaryPushButton]; 387 [pause setImagePosition: NSImageOnly]; 388 [pause setImage: image]; 389 [pause setTarget: self]; 390 [pause setAction: @selector(pause:)]; 391 [[window contentView] addSubview: pause]; 392 393 path = [bundle pathForResource: @"next" ofType: @"tiff"]; 394 image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease]; 395 if(image == nil) NSLog(@"cannot load next.tiff"); 396 frame = NSMakeRect(95, 5, 30, 30); 397 next = [[NSButton alloc] initWithFrame: frame]; 398 [next setButtonType: NSMomentaryPushButton]; 399 [next setImagePosition: NSImageOnly]; 400 [next setImage: image]; 401 [next setTarget: self]; 402 [next setAction: @selector(next:)]; 403 [[window contentView] addSubview: next]; 404 405 path = [bundle pathForResource: @"stop" ofType: @"tiff"]; 406 image = [[[NSImage alloc] initWithContentsOfFile: path] autorelease]; 407 if(image == nil) NSLog(@"cannot load stop.tiff"); 408 frame = NSMakeRect( 125, 5, 30, 30); 409 stop = [[NSButton alloc] initWithFrame: frame]; 410 [stop setButtonType: NSMomentaryPushButton]; 411 [stop setImagePosition: NSImageOnly]; 412 [stop setImage: image]; 413 [stop setTarget: self]; 414 [stop setAction: @selector(stop:)]; 415 [[window contentView] addSubview: stop]; 416 417 [window orderFront: self]; 418 [window setFrameAutosaveName: @"CDPlayerWindow"]; 419 [window setFrameUsingName: @"CDPlayerWindow"]; 420} 421 422// 423// services methods 424// 425 426- (void) getTOC: (NSPasteboard *) pboard 427 userData: (NSString *) userData 428 error: (NSString **) error 429{ 430 TrackList *tl = [TrackList sharedTrackList]; 431 int i, rows = [tl numberOfTracksInTOC]; 432 NSMutableArray *array; 433 434 /* 435 * If we don't have any rows, there is probably no CD. 436 */ 437 if (rows == 0) { 438 *error = _(@"No Audio CD found."); 439 return; 440 } 441 442 array = [[NSMutableArray alloc] init]; 443 444 for (i = 0; i < rows; i++) { 445 [array addObject: [NSNumber numberWithInt: i]]; 446 } 447 448 /* 449 * This is a small hack, but we can clean this up later. 450 */ 451 if (![tl tableView: nil writeRows: array toPasteboard: pboard]) { 452 *error = _(@"Could not write TOC to pasteboard."); 453 } else { 454 } 455 456 RELEASE(array); 457} 458 459- (void) playCD: (NSPasteboard *) pboard 460 userData: (NSString *) userData 461 error: (NSString **) error 462{ 463 NSArray *types = [pboard types]; 464 465 // If CD is currently playing, we reject the request 466 if([drive currentState] == AUDIOCD_PLAYING) { 467 *error = _(@"Player.alreadyPlaying"); 468 return; 469 } 470 471 /* 472 * Do we have at least one valid pasteboard type? 473 */ 474 if (![types containsObject: NSFilenamesPboardType] && 475 ![types containsObject: NSStringPboardType]) { 476 *error = _(@"Player.noValidPboardType"); 477 return; 478 } 479 480 /* 481 * Try to add as much as possible, i.e. even if one pasteboard 482 * type fails try the other one (if it exists in the pasteboard). 483 */ 484 if ([types containsObject: NSFilenamesPboardType] || 485 [types containsObject: NSStringPboardType]) { 486 // Get the device name from the pboard 487 NSString *device = nil; 488 NSArray *devices = [pboard propertyListForType: NSFilenamesPboardType]; 489 if (devices != nil) { 490 if ([devices count] != 1) { 491 *error = _(@"Player.tooManyFileNames"); 492 return; 493 } 494 device = [devices objectAtIndex: 0]; 495 } else { 496 device = [pboard propertyListForType: NSStringPboardType]; 497 } 498 if (device == nil) { 499 *error = _(@"Player.noDeviceNameFound"); 500 return; 501 } 502 autoPlay = YES; 503 [drive stopPolling]; 504 [drive startPollingWithPreferredDevice: device]; 505 } 506} 507 508// 509// class methods 510// 511+ (Player *) sharedPlayer 512{ 513 if (!sharedPlayer) { 514 sharedPlayer = [[Player alloc] init]; 515 } 516 return sharedPlayer; 517} 518 519 520@end 521