iOS懸浮時(shí)鐘(附實(shí)現(xiàn)源碼)

1. 首先先看使用效果

image.png

如果你不是開(kāi)發(fā)者,只是想使用這個(gè)app,可以點(diǎn)擊下方鏈接下載安裝使用。核心功能都是免費(fèi)的,可以放心食用。 如果鏈接不能直接跳轉(zhuǎn),選擇在Safari中打開(kāi)
iPhone需要iOS14 及以上版本才支持畫(huà)中畫(huà)功能。

懸浮時(shí)鐘-搶購(gòu)秒殺助手

也可以直接在App Store搜索:懸浮秒殺助手,第一個(gè)app就是

2. 開(kāi)始之前

  1. 首先iOS 決定了開(kāi)發(fā)者沒(méi)法為用戶(hù)提供一個(gè)系統(tǒng)級(jí)別view ,也就是不能實(shí)現(xiàn)所謂的懸浮窗等功能。 所以開(kāi)發(fā)者只能AVPictureInPictureController 來(lái)實(shí)現(xiàn)類(lèi)似的效果。
  2. AVPictureInPictureController 一般用于實(shí)現(xiàn)視頻畫(huà)中畫(huà)播放功能。 因此要想實(shí)現(xiàn)時(shí)鐘的效果, 這個(gè)問(wèn)題就變成了制作一個(gè)播放時(shí)間的視頻,然后進(jìn)行播放。

這里需要思考一個(gè)問(wèn)題 : 如何保持視頻時(shí)間和系統(tǒng)時(shí)間的同步,讓用戶(hù)暫停 快進(jìn) 操作不影響到時(shí)間變化

這個(gè)問(wèn)題是這個(gè)功能的實(shí)際難點(diǎn), 你肯定不希望這個(gè)時(shí)間有誤差或者是受用戶(hù)影響(比如用戶(hù)暫停了視頻播放,時(shí)間就不動(dòng)了)

3. 實(shí)現(xiàn)思路

  1. 首先想到的肯定是制作一段視頻,視頻內(nèi)容就是時(shí)間流, 速率和真實(shí)時(shí)間一致。在用戶(hù)操作后進(jìn)行播放。 實(shí)際上這個(gè)方案行不通,因?yàn)橛脩?hù)很容易通過(guò)暫??爝M(jìn)等操作導(dǎo)致時(shí)間不同步
  2. 如果只是在App內(nèi)部顯示當(dāng)前時(shí)間,我們只需要通過(guò)UILabel定時(shí)器就很容易的進(jìn)行實(shí)現(xiàn)。 如果能在 AVPictureInPictureController 控制的圖層上添加視圖,也就能很方便的達(dá)到我們的效果。 但是經(jīng)過(guò)實(shí)際研究, 雖然可以通過(guò)比較hack的方式在畫(huà)中畫(huà)上添加view,但是會(huì)有很多問(wèn)題,比如用戶(hù)縮放,view并不會(huì)隨之縮放。效果比較差

以上兩種方案是最初的思路,都沒(méi)能很好的達(dá)到目的。 最終我采用了視頻實(shí)時(shí)渲染的方案,當(dāng)用戶(hù)開(kāi)始使用畫(huà)中畫(huà),我們實(shí)時(shí)渲染當(dāng)前時(shí)間。并且我們?cè)谝曨l停止播放的時(shí)候,也要刷新屏幕時(shí)間。

這樣,呈現(xiàn)給用戶(hù)的就是一個(gè)隨著系統(tǒng)時(shí)間實(shí)時(shí)刷新的懸浮窗口了

思路有了,你可以通過(guò)下面的鏈接,把工程下載到本地嘗試一下。需要注意的是iPhone在iOS14 才開(kāi)始支持畫(huà)中畫(huà)功能。

如果你使用模擬器測(cè)試,可以使用iPad的模擬器運(yùn)行項(xiàng)目,iphone的模擬器目前不支持畫(huà)中畫(huà)功能

Demo鏈接

4. 代碼說(shuō)明

1. 先看目錄結(jié)構(gòu),非常簡(jiǎn)單。ViewController 管理我們demo首頁(yè)的視圖和操作邏輯

我們會(huì)使用到temp.mov 作為視頻進(jìn)行畫(huà)中畫(huà)的呈現(xiàn),這個(gè)視頻非常短,本身也沒(méi)有任何內(nèi)容。我們所顯示的時(shí)鐘內(nèi)容全部是通過(guò)TimeVideoCompositionTimeVideoCompositionInstruction 這兩個(gè)類(lèi)實(shí)現(xiàn)的 。后面會(huì)我們會(huì)看到在如何去使用它們

image.png

2. 然后我們進(jìn)入Viewcontroller 看一下對(duì)應(yīng)的方法。

首先配置UI和加載Video

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        setupVideo()
    }

等到視頻加載完成,我們調(diào)用setupComposition方法進(jìn)行視頻合成的的配置。 簡(jiǎn)單來(lái)說(shuō),我們使用自己創(chuàng)建的 TimeVideoComposition 作為視屏的合成器,然后通過(guò)TimeVideoCompositionInstruction 進(jìn)行視頻合成。

    func setupComposition()  {

        // For best performance, ensure that the duration and tracks properties of the asset are already loaded before invoking this method.
        videoComposition = AVMutableVideoComposition(propertiesOf: asset!)
        let instructions = videoComposition.instructions as! [AVVideoCompositionInstruction]
        var newInstructions: [AVVideoCompositionInstructionProtocol] = []

        guard let instruction = instructions.first else {
            return
        }
        let layerInstructions = instruction.layerInstructions
        // TrackIDs
        var trackIDs: [CMPersistentTrackID] = []
        for layerInstruction in layerInstructions {
            trackIDs.append(layerInstruction.trackID)
        }
        timeInstruction = TimeVideoCompositionInstruction(trackIDs as [NSValue], timeRange: instruction.timeRange)
        timeInstruction.layerInstructions = layerInstructions
        newInstructions.append(timeInstruction)
        videoComposition.instructions = newInstructions

        self.videoComposition?.customVideoCompositorClass = TimeVideoComposition.self
        item?.videoComposition = videoComposition
    }

想要深入了解的可以參考文末鏈接

3. 上一步我們使用到了TimeVideoCompositionInstruction,核心部分就是在這個(gè)類(lèi)中實(shí)現(xiàn)。當(dāng)視頻需要我們提供界面的時(shí)候, 我們?cè)?code>getPixelBuffer 提供對(duì)應(yīng)的界面。這個(gè)工程中我們使用CoreText 繪制了當(dāng)前時(shí)間

  func getPixelBuffer(_ renderContext: AVVideoCompositionRenderContext) -> CVPixelBuffer? {
    .......
  } 

看到這里,基本流程就已經(jīng)走通了。 用戶(hù)使用畫(huà)中畫(huà)功能,視頻調(diào)用合成器進(jìn)行顯示內(nèi)容的處理,我們提供需要顯示的畫(huà)面

4. 在視頻加載完成以后,除了配置合成器,我們還開(kāi)啟了一個(gè)定時(shí)器。

DispatchQueue.main.async {
   //配置合成器
    self.setupComposition()
   //創(chuàng)建定時(shí)器
    self.createDisplayLink()
}

定時(shí)器開(kāi)啟后,我們每次都需要給AVPlayerItem 進(jìn)行賦值, 以此觸發(fā)視頻的刷新。 這樣不論用戶(hù)是否播放暫停視頻,我們都能在界面上顯示最新的內(nèi)容

    @objc func refresh(displaylink: CADisplayLink) {
        reloadTime()
        item?.videoComposition = videoComposition
    }

5. 小結(jié)

實(shí)際流程非常簡(jiǎn)單,但是實(shí)現(xiàn)寫(xiě)代碼的時(shí)候查閱了大量文檔視頻,用很多方式實(shí)現(xiàn)了這個(gè)功能,最終覺(jué)的這種方式效果最好。 其中有難點(diǎn)的就是理解AVVideoCompositing AVVideoCompositionInstructionProtocol 的使用, 對(duì)這部分有疑惑的小伙伴可以多參考官方的文檔和視頻。

同時(shí)可以下載我們的App體驗(yàn)一下(說(shuō)不定能搶個(gè)茅臺(tái) ( ̄▽?zhuān)?~*)

懸浮時(shí)鐘-搶購(gòu)秒殺助手

x. 一些參考鏈接

Building a Real-Time Video Editor with AVFoundation
Working with Media in AV Foundation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容