83、多媒体开发指南:AV Foundation与音乐库操作

多媒体开发指南:AV Foundation与音乐库操作

1. 图层与动画添加

在架构中引入更多图层是可行的。以下是添加文本图层的代码:

let lay = CATextLayer()
lay.string = "This is cool!"
lay.alignmentMode = .center
lay.foregroundColor = UIColor.black.cgColor
lay.frame = child.bounds
child.addSublayer(lay)

现在,导出的版本会在前面叠加显示 “This is cool!” 字样。

我们还可以进一步为图层添加动画。例如,让文本在视频开始一秒后慢慢淡入,代码如下:

let ba = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
ba.duration = 1
ba.fromValue = 0
ba.toValue = 1
ba.beginTime = AVCoreAnimationBeginTimeAtZero + 1 // 关键
ba.fillMode = .backwards
lay.add(ba, forKey: nil)

2. AVPlayerLayer 的使用

2.1 基本介绍

AVPlayer 并非界面对象,对应的界面对象是 AVPlayerLayer(CALayer 的子类)。它没有让用户播放、暂停电影以及查看进度的控件,仅用于显示电影,是 AV Foundation 媒体世界和用户可见的 CALayer 世界之间的桥梁。

2.2 不使用 AVPlayerViewController 显示视频

以下是不使用 AVPlayerViewController 显示视频的代码:

let m = Bundle.main.url(forResource:"ElMirage", withExtension:"mp4")!
let asset = AVURLAsset(url:m)
let item = AVPlayerItem(asset:asset)
let p = AVPlayer(playerItem:item)
self.player = p // 可能之后需要引用
let lay = AVPlayerLayer(player:p)
lay.frame = CGRect(10,10,300,200)
self.playerLayer = lay // 可能之后需要引用
self.view.layer.addSublayer(lay)

若要防止视频准备好显示时出现闪屏,可以通过 KVO 监听 isReadyForDisplay 属性,在其变为 true 后再将 AVPlayerLayer 添加到界面。

2.3 优化代码

在 WWDC 2016 视频中,Apple 建议先创建没有 AVPlayerItem 的 AVPlayer,再创建 AVPlayerLayer,最后将 AVPlayerItem 分配给 AVPlayer,代码如下:

let m = Bundle.main.url(forResource:"ElMirage", withExtension:"mp4")!
let asset = AVURLAsset(url:m)
let item = AVPlayerItem(asset:asset)
let p = AVPlayer() // *
self.player = p
let lay = AVPlayerLayer(player:p)
lay.frame = CGRect(10,10,300,200)
self.playerLayer = lay
p.replaceCurrentItem(with: item) // *
self.view.layer.addSublayer(lay)

这样做可以提高效率,因为当 AVPlayer 没有关联的 AVPlayerLayer 时,它会认为 AVAsset 只有音频轨道重要,之后再关联 AVPlayerLayer 时,就需要匆忙获取视频轨道。

2.4 控制视频播放

视频显示后,需要控制其播放。可以通过调用 play 或设置 AVPlayer 的 rate 来开始播放,以下是一个简单的播放/暂停按钮的代码:

@IBAction func doButton (_ sender: Any) {
    let rate = self.player.rate
    self.player.rate = rate < 0.01 ? 1 : 0
}

还可以让用户将播放头跳回电影开头,代码如下:

@IBAction func restart (_ sender: Any) {
    let item = self.player.currentItem!
    item.seek(to:CMTime(seconds:0, preferredTimescale:600))
}

2.5 支持画中画功能

若要让 AVPlayerLayer 支持画中画功能,需要使用 AVKit 提供的 AVPictureInPictureController,代码如下:

if AVPictureInPictureController.isPictureInPictureSupported() {
    let pic = AVPictureInPictureController(playerLayer: self.playerLayer)
    self.pic = pic
}

需要自己提供画中画按钮,并在点击时调用 startPictureInPicture 方法:

@IBAction func doPicInPic(_ sender: Any) {
    if self.pic.isPictureInPicturePossible {
        self.pic.startPictureInPicture()
    }
}

还可以设置自己为 AVPictureInPictureController 的代理,以便在画中画窗口的生命周期阶段进行界面调整。

3. AV Foundation 的其他功能

3.1 主要功能列表

  • 从电影中提取单张图像(缩略图),使用 AVAssetImageGenerator。
  • 以不同格式导出电影,或通过缓冲区读写原始未压缩数据,使用 AVAssetExportSession、AVAssetReader、AVAssetReaderOutput、AVAssetWriter、AVAssetWriterInput 等。
  • 通过设备硬件捕获音频、视频和静态图像,使用 AVCaptureSession 等。
  • 接入正在捕获或播放的视频和音频,包括将视频帧捕获为静态图像,使用 AVPlayerItemVideoOutput、AVCaptureVideoDataOutput 等。

3.2 相关视频

AV Foundation 的媒体功能在经典的 WWDC 视频中得到了很好的总结: https://developer.apple.com/videos/play/wwdc2011/415/

4. UIVideoEditorController 的使用

4.1 基本介绍

UIVideoEditorController 是一个视图控制器,用于让用户修剪视频。它的视图和内部行为不受开发者控制,也不建议子类化。需要通过其代理进行响应。

4.2 使用步骤

  1. 调用 canEditVideo(atPath:) 方法检查视频是否可编辑:
let path = Bundle.main.path(forResource:"ElMirage", ofType: "mp4")!
let can = UIVideoEditorController.canEditVideo(atPath:path)
if !can {
    print("can't edit this video")
    return
}
  1. 创建 UIVideoEditorController 实例,设置代理和视频路径,并根据设备类型设置模态展示样式:
let vc = UIVideoEditorController()
vc.delegate = self
vc.videoPath = path
if UIDevice.current.userInterfaceIdiom == .pad {
    vc.modalPresentationStyle = .popover
}
self.present(vc, animated: true)
if let pop = vc.popoverPresentationController {
    let v = sender as! UIView
    pop.sourceView = v
    pop.sourceRect = v.bounds
    pop.delegate = self
}

4.3 代理方法

需要实现三个代理方法,并在其中关闭展示的视图:

func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) {
    self.dismiss(animated:true)
}

func videoEditorController(_ editor: UIVideoEditorController, didFailWithError error: Error) {
    self.dismiss(animated:true)
}

func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath path: String) {
    self.dismiss(animated:true)
    if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path) {
        UISaveVideoAtPathToSavedPhotosAlbum(path, self, #selector(savedVideo), nil)
    } else {
        // 无法保存到相册,尝试其他操作
    }
}

@objc func savedVideo(at path:String, withError error:Error?, ci:UnsafeMutableRawPointer) {
    if let error = error {
        print("error: \(error)")
    }
}

5. 音乐库操作

5.1 功能概述

iOS 设备可用于存储和播放音乐、播客和有声读物,这些构成了设备的音乐库。开发者可以使用 Media Player 框架实现以下功能:
- 探索音乐库。
- 播放音乐库中的项目。
- 控制音乐应用的音乐播放器。
- 提供标准界面让用户选择音乐库项目。

5.2 音乐库授权

5.2.1 授权步骤
  1. 在 Info.plist 中添加 “Privacy — Media Library Usage Description” 键(NSAppleMusicUsageDescription),向用户说明访问音乐库的原因。
  2. 调用 MPMediaLibrary 的 authorizationStatus 类方法检查授权状态,结果可能为:
    • .notDetermined:从未请求过授权,应请求授权,系统会弹出授权提示框。
    • .authorized:已获得授权,可以访问音乐库。
    • .denied:被拒绝授权,若应用依赖音乐库访问,可以弹出提示框请求授权,甚至可以引导用户到设置应用中进行授权。
    • .restricted:被拒绝授权,且用户可能无权授权,最好不做处理。
  3. 若授权状态为 .notDetermined,调用 MPMediaLibrary 的 requestAuthorization 方法请求授权,并传入完成函数:
func checkForMusicLibraryAccess(andThen f:(()->())? = nil) {
    let status = MPMediaLibrary.authorizationStatus()
    switch status {
    case .authorized: f?()
    case .notDetermined:
        MPMediaLibrary.requestAuthorization() { status in
            if status == .authorized {
                DispatchQueue.main.async {
                    f?()
                }
            }
        }
    case .restricted: break // 不做处理
    case .denied: break // 不做处理,或请求授权
    }
}

5.3 探索音乐库

5.3.1 主要类介绍
  • MPMediaEntity:抽象类,是音乐库中所有项目的基类,有两个具体子类。
    • MPMediaItem:单个项目(“歌曲”)。
    • MPMediaCollection:MPMediaItems 的有序列表,类似于数组。
5.3.2 属性获取

MPMediaEntity 及其子类可以通过键值对(属性)描述自身。可以使用 value(forProperty:) 方法获取单个属性的值,使用 enumerateValues(forProperties:using:) 方法获取多个属性的值。同时,它们也有与属性键名对应的实例属性,方便使用。

5.3.3 项目类型

MPMediaItem 有类型(mediaType 或 MPMediaItemPropertyMediaType),如音乐、播客或有声读物。不同类型的项目有略微不同的属性。

5.3.4 播放列表

播放列表是 MPMediaPlaylist,是 MPMediaCollection 的子类,其属性包括名称和属性集,用于判断是否为 “智能” 播放列表。

5.3.5 艺术作品图像

项目的艺术作品图像可以通过 MPMediaItemArtwork 类的实例获取,但实际获取的图像大小可能不符合预期,可能需要进一步缩放。

5.4 查询音乐库

5.4.1 形成查询

有三种主要方式形成查询:
- 无限制查询:使用 MPMediaQuery() 创建简单查询,获取音乐库中的所有内容。
- 便捷构造函数:使用 MPMediaQuery 提供的类方法,如 songs、podcasts、audiobooks 等,获取音乐库的有限子集。
- 使用过滤谓词:通过附加一个或多个 MPMediaPropertyPredicate 实例来更精确地限制查询。谓词有三个方面:属性、值和比较类型(可选)。

查询还可以根据 groupingType 对结果进行分组,可选值包括 .title、.album、.artist 等。

5.4.2 执行查询

执行查询只需获取查询的属性:
- 若不关心查询返回的组,可以获取 its items,即 MPMediaItems 数组。
- 若关心组,可以获取 its collections,即 MPMediaItemCollections 数组,每个集合代表一个组。

5.4.3 查询示例

以下是一些查询示例:
- 获取所有专辑的标题:

let query = MPMediaQuery.albums()
guard let result = query.collections else {return}
for album in result {
    print(album.representativeItem!.albumTitle!)
}
  • 获取名称包含 “Beethoven” 的所有专辑的标题:
let query = MPMediaQuery.albums()
let hasBeethoven = MPMediaPropertyPredicate(value:"Beethoven",
    forProperty:MPMediaItemPropertyAlbumTitle,
    comparisonType:.contains)
query.addFilterPredicate(hasBeethoven)
guard let result = query.collections else {return}
for album in result {
    print(album.representativeItem!.albumTitle!)
}
  • 获取包含名称包含 “Sonata” 的歌曲的所有专辑的标题:
let query = MPMediaQuery.albums()
let hasSonata = MPMediaPropertyPredicate(value:"Sonata",
    forProperty:MPMediaItemPropertyTitle,
    comparisonType:.contains)
query.addFilterPredicate(hasSonata)
guard let result = query.collections else {return}
for album in result {
    print(album.representativeItem!.albumTitle!)
}
  • 打印第一个匹配专辑中所有匹配歌曲的标题:
let album = result[0]
for song in album.items {
    print(song.title!)
}

5.5 音乐库操作流程图

graph TD;
    A[开始] --> B[检查授权状态];
    B --> C{授权状态};
    C -->|.notDetermined| D[请求授权];
    D --> E{授权结果};
    E -->|.authorized| F[访问音乐库];
    C -->|.authorized| F;
    C -->|.denied| G[请求授权或提示用户];
    C -->|.restricted| H[不做处理];
    F --> I[形成查询];
    I --> J{查询方式};
    J -->|无限制查询| K[获取所有内容];
    J -->|便捷构造函数| L[获取有限子集];
    J -->|过滤谓词| M[精确限制查询];
    K --> N[执行查询];
    L --> N;
    M --> N;
    N --> O{结果类型};
    O -->|items| P[获取 MPMediaItems 数组];
    O -->|collections| Q[获取 MPMediaItemCollections 数组];
    P --> R[处理项目];
    Q --> S[处理集合];

以上就是关于多媒体开发中 AV Foundation 和音乐库操作的详细介绍,希望能帮助开发者更好地实现相关功能。

6. 常见问题解答

6.1 AV Foundation 相关

问题 解答
添加 AVPlayerLayer 时出现闪屏怎么办? 可以通过 KVO 监听其 isReadyForDisplay 属性,在该属性变为 true 后再将 AVPlayerLayer 添加到界面。
如何提高 AVPlayer 加载视频的效率? 可以先创建没有 AVPlayerItem 的 AVPlayer,再创建 AVPlayerLayer,最后将 AVPlayerItem 分配给 AVPlayer。这样当 AVPlayer 没有关联的 AVPlayerLayer 时,它会认为 AVAsset 只有音频轨道重要,之后再关联 AVPlayerLayer 时,就不会匆忙获取视频轨道。
怎样控制视频的播放和暂停? 可以通过调用 play 方法或设置 AVPlayer 的 rate 属性来控制视频的播放和暂停。例如:
swift<br>@IBAction func doButton (_ sender: Any) {<br> let rate = self.player.rate<br> self.player.rate = rate < 0.01 ? 1 : 0<br>}<br>
如何实现视频的画中画功能? 首先要确保设备支持画中画功能,然后使用 AVKit 提供的 AVPictureInPictureController。示例代码如下:
swift<br>if AVPictureInPictureController.isPictureInPictureSupported() {<br> let pic = AVPictureInPictureController(playerLayer: self.playerLayer)<br> self.pic = pic<br>}<br> 同时,需要自己提供画中画按钮,并在点击时调用 startPictureInPicture 方法。

6.2 UIVideoEditorController 相关

|问题|解答|
|如何判断视频是否可编辑?|调用 UIVideoEditorController 的 canEditVideo(atPath:) 方法进行检查。示例:
swift<br>let path = Bundle.main.path(forResource:"ElMirage", ofType: "mp4")!<br>let can = UIVideoEditorController.canEditVideo(atPath:path)<br>if !can {<br> print("can't edit this video")<br> return<br>}<br> |
|UIVideoEditorController 的代理方法有哪些需要实现?|需要实现三个代理方法:videoEditorControllerDidCancel( :)、videoEditorController( :didFailWithError:) 和 videoEditorController(_:didSaveEditedVideoToPath:),并在这些方法中关闭展示的视图。|
|编辑后的视频保存到哪里了?|编辑后的视频会保存到应用的临时目录,你可以根据需求将其复制到其他地方,如用户的相机胶卷相册。|

6.3 音乐库操作相关

|问题|解答|
|访问音乐库需要什么授权?|需要在 Info.plist 中添加 “Privacy — Media Library Usage Description” 键(NSAppleMusicUsageDescription),并调用 MPMediaLibrary 的 authorizationStatus 类方法检查授权状态,根据不同状态进行相应处理。|
|如何查询音乐库中的特定内容?|可以通过无限制查询、便捷构造函数或使用过滤谓词来形成查询,然后执行查询获取结果。例如,获取名称包含 “Beethoven” 的所有专辑的标题:
swift<br>let query = MPMediaQuery.albums()<br>let hasBeethoven = MPMediaPropertyPredicate(value:"Beethoven",<br> forProperty:MPMediaItemPropertyAlbumTitle,<br> comparisonType:.contains)<br>query.addFilterPredicate(hasBeethoven)<br>guard let result = query.collections else {return}<br>for album in result {<br> print(album.representativeItem!.albumTitle!)<br>}<br> |
|MPMediaItem 和 MPMediaCollection 有什么区别?|MPMediaItem 代表单个项目(如歌曲),而 MPMediaCollection 是 MPMediaItems 的有序列表,类似于数组。|

7. 性能优化建议

7.1 AV Foundation 性能优化

  • 延迟添加 AVPlayerLayer :如前面提到的,通过 KVO 监听 isReadyForDisplay 属性,在视频准备好显示时再添加 AVPlayerLayer,避免闪屏问题,同时也能优化用户体验。
  • 合理使用 AVPlayerItem :采用先创建无 AVPlayerItem 的 AVPlayer,再创建 AVPlayerLayer,最后分配 AVPlayerItem 的方式,提高视频加载效率。
  • 异步查询 :查询音乐库可能会比较耗时,建议在后台线程进行,避免阻塞主线程。例如:
DispatchQueue.global().async {
    let query = MPMediaQuery.albums()
    guard let result = query.collections else { return }
    DispatchQueue.main.async {
        // 在主线程更新 UI
        for album in result {
            print(album.representativeItem!.albumTitle!)
        }
    }
}

7.2 音乐库操作性能优化

  • 缓存查询结果 :如果某些查询结果经常使用,可以将其缓存起来,避免重复查询,提高查询效率。
  • 合理使用过滤谓词 :在使用过滤谓词时,尽量减少不必要的过滤条件,避免查询过于复杂,影响性能。

8. 总结

通过对 AV Foundation 和音乐库操作的学习,我们了解了如何在应用中添加图层和动画,使用 AVPlayerLayer 播放视频,实现视频编辑和画中画功能,以及对音乐库进行授权、探索和查询等操作。同时,我们还掌握了一些常见问题的解决方法和性能优化建议。

在实际开发中,开发者可以根据具体需求选择合适的功能和方法,结合性能优化策略,为用户提供更好的多媒体体验。希望以上内容能对开发者有所帮助,让大家在多媒体开发的道路上更加顺利。

9. 未来展望

随着移动设备性能的不断提升和用户对多媒体需求的日益增长,多媒体开发领域将迎来更多的机遇和挑战。未来,我们可以期待以下方面的发展:

9.1 AV Foundation 发展趋势

  • 更高质量的视频处理 :支持更高分辨率、帧率和编码格式的视频处理,为用户带来更加清晰、流畅的视频体验。
  • 增强现实(AR)与视频的融合 :将 AR 技术与视频播放和编辑相结合,创造出更加沉浸式的多媒体体验。
  • 智能视频分析 :利用人工智能和机器学习技术,对视频内容进行智能分析,实现自动剪辑、场景识别等功能。

9.2 音乐库操作发展趋势

  • 云音乐库的深度整合 :随着云存储和流媒体技术的发展,音乐库将更多地与云服务相结合,实现跨设备的音乐同步和播放。
  • 个性化音乐推荐 :通过对用户音乐偏好的分析,提供更加个性化的音乐推荐服务,提高用户的音乐发现能力。
  • 音乐社交功能的增强 :增加音乐社交功能,让用户可以分享自己喜欢的音乐、创建音乐社区等,增强用户之间的互动和交流。

总之,多媒体开发领域充满了无限的可能性,开发者需要不断学习和探索,跟上技术发展的步伐,为用户带来更加优质的多媒体应用。

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值