Mac application development notes 2

Mac开发不同于iOS开发,无需考虑太多性能消耗、动画特效、流畅性问题,但也引入了许多新的知识点,Apple提供给Mac开发的许多权限是iOS没有的。

这两天在开发一款本地音乐播放器,简单的界面,支持数据永久化缓存、双击播放器界面选择文件导入、或从Mac文件目录里选择文件直接拖拽到音乐播放器里播放。
效果如下:


Screen Shot 2018-10-03 at 2.24.38 AM.png

当然也包括Status bar里的自定义控件,效果和QQ音乐相仿,大致如下:


status bar.png

1、文件导出到播放器很简单,只需几行代码即可实现,首先监听Mac鼠标点击事件,当触发点击事件时,判断是否是双击,如果是则处理点击事件,代码如下:

- (void)mouseUp:(NSEvent *)event {
    if(event.clickCount >= 2) {
        NSOpenPanel *openPanel = [NSOpenPanel openPanel];
        openPanel.canChooseFiles = YES;
        openPanel.canChooseDirectories = YES;
        openPanel.canResolveUbiquitousConflicts = YES;
        // 允许同时导入多个文件
        openPanel.allowsMultipleSelection = YES;
        NSInteger result = [openPanel runModal];
        if(result == NSModalResponseOK) {
            [self  resetPlayerWithURL:openPanel.URL];
        }
    }
}

音乐播放使用的是AVFoundation框架,AVPlayer、AVPlayerItem来实现的,因为本地音乐文件有的是有封面等信息的,如下图:

cover.png

使用了AVURLAsset来获取导入音频文件中的歌曲信息
4D0B59FAE1706D184BDE6D386C0BC694.png

代码如下:

- (AVURLAsset *)assetWithURL:(NSURL *)url {
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
    for(NSString *format in asset.availableMetadataFormats) {
        for(AVMetadataItem *metaDataItem in [asset metadataForFormat:format]) {
            // cover
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyArtwork]) {
                NSImage *image = [[NSImage alloc] initWithData:(NSData *)metaDataItem.value];
                self.coverImage.image = image ? image : [NSImage imageNamed:@"IMG_2567"];
            }
            
            // name
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyTitle]) {
                self.songNameLabel.stringValue = (NSString *)metaDataItem.value;
            }
            
            // musician
            if([metaDataItem.commonKey isEqualToString:AVMetadataCommonKeyArtist]) {
//                self.artistLabel.stringValue = (NSString *)metaDataItem.value;
            }            
        }
    }
    return asset;
}

AVFoundation框架里的AVMetadataFormat文件里提供了很多MedadataKey,可根据需求获取

// Metadata common keys
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyTitle                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCreator                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySubject                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyDescription                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyPublisher                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyContributor                                NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCreationDate                               NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLastModifiedDate                           NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyType                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyFormat                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyIdentifier                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySource                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLanguage                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyRelation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyLocation                                   NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyCopyrights                                 NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyAlbumName                                  NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyAuthor                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyArtist                                     NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyArtwork                                    NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyMake                                       NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeyModel                                      NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT AVMetadataKey const AVMetadataCommonKeySoftware                                   NS_AVAILABLE(10_7, 4_0);

2、拖动文件到播放器的流程,首先在播放器NSView界面,初始化的时候注册

- (instancetype)initWithCoder:(NSCoder *)decoder {
    if(self = [super initWithCoder:decoder]) {
        // 注册所有文件格式
        NSString *UTTypeString = (__bridge NSString *)kUTTypeURL;
        [self registerForDraggedTypes:[NSArray arrayWithObject:UTTypeString]];
    }
    return self;
}

其次实现拖动文件的代理

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
    NSPasteboard *pasteboard = [sender draggingPasteboard];
    NSString *audioVisualContent = (__bridge NSString *)kUTTypeAudiovisualContent;
    NSDictionary *filteringOptions =[NSDictionary dictionaryWithObject:audioVisualContent forKey:NSPasteboardURLReadingFileURLsOnlyKey];
    if([pasteboard canReadObjectForClasses:@[[NSURL class]] options:filteringOptions]) {
        return NSDragOperationCopy;
    }
    return NSDragOperationNone;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
    NSArray *aboardArray = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
    NSURL *url = [NSURL fileURLWithPath:aboardArray.firstObject];
    if(self.draggingFile) {
        self.draggingFile(url);
    }
    return YES;
}

这里的draggingFile是一个block,当系统检测到有文件拖动到播放器界面时,获取音频文件的本地路径,回掉给播放器播放

typedef void(^PasteboardResponsedBlock)(NSURL *dragingPath);
@property (nonatomic, strong) PasteboardResponsedBlock draggingFile;

3、添加播放器到Mac中,Xcode - Target - Info, 选择Document Types,增加一个types


Screen Shot 2018-10-03 at 3.09.17 AM.png

设置图标,Extensions是支持播放的音频文件格式,这里仅支持mp3格式的音频文件,设置完成后,当我们右键点击本地mp3文件弹出菜单选择播放器时,就可以看到自定义的播放器了

Screen Shot 2018-10-03 at 3.06.36 AM.png

接下来选择AppDelegat文件,实现如下方法,用于监听双击打开音频文件的Mac本地路径:

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
    // 双击选择音频文件的本地路径, 回调给AVPlayer播放
    /Users/xxx/Desktop/timegoesby.mp3
    return YES;
}

当然,在我们运行App后,点击关闭无法再次打开时,需要在AppDelegate里面,在与AppDelegate绑定的NSWindowController里,添加如下代码,这样当关闭App,在Dock里点击App图标依然可以打开

- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
    [self.windowController.window makeKeyAndOrderFront:nil];
    return YES;
}

4、当然如果设置了App图标,在Dock里依然可以显示该App

Dock.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容