简体   繁体   中英

Swift - Workaround/Alternative to M3u8 to play mp4 segment or merge segments into mp4

I used AVAssetExportSession to download a session URL but the issue that you can't download live stream so to get around it, the live stream is split into 10 seconds mp4 segments that are downloaded using an m3u8 to create the URLs. I then use AVAssetExportSession to merge those mp4 segments.

I can merge those clips one by one into one mp4 file which is what I want but as the file gets bigger, the longer it takes as I am dealing with thousands of segments which becomes unpractical.

I thought about using AVplayerLooper but I cannot scrub, rewind or forward through the mp4 segment like a single video.

Is there a way to combine the mp4 clips together to play as one video as the m3u8 does without merging? or is there a fast way to merge videos?

Note : The server uses FFmpeg but I am not allowed to use FFmpeg or pods in the app.

below is the function to merge videos

var mp4Array: [AVAsset] = []
var avAssetExportSession: AVAssetExportSession?

var firstAsset: AVAsset?
var secondAsset: AVAsset?

func mergeVideos() {

    firstAsset = mp4Array.first
    secondAsset = mp4Array[1]

    guard let firstAsset = firstAsset, let secondAsset = secondAsset else { return }
    let mixComposition = AVMutableComposition()

    guard let firstTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else {return}

    do {

        try firstTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: firstAsset.duration),
                                       of: firstAsset.tracks(withMediaType: .video)[0],
                                       at: CMTime.zero)

    } catch {
        print("Couldn't load track 1")
        return
    }

    guard let secondTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else {return}

    do {
        try secondTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: secondAsset.duration),
                                        of: secondAsset.tracks(withMediaType: .video)[0],
                                        at: firstAsset.duration)
    } catch {
        print("couldn't load track 2")
        return
    }

    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: CMTimeAdd(firstAsset.duration, secondAsset.duration))

    let firstAssetInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
    firstAssetInstruction.setOpacity(0.0, at: firstAsset.duration)

    let secondAssetInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: secondTrack)

    mainInstruction.layerInstructions = [firstAssetInstruction, secondAssetInstruction]
    let mainComposition = AVMutableVideoComposition()
    mainComposition.instructions = [mainInstruction]
    mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
    mainComposition.renderSize = firstTrack.naturalSize

    guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let url = documentDirectory.appendingPathComponent("MergedVideos/mergeVideo\(videoInt).mp4")

    guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else {return}

    exporter.outputURL = url
    exporter.outputFileType = AVFileType.mp4
    exporter.shouldOptimizeForNetworkUse = true
    exporter.videoComposition = mainComposition

    exporter.exportAsynchronously {

        if exporter.status == .completed {
            let avasset = AVAsset(url:url)
            self.mergeUrl = avasset
            if self.mp4Array.count > 1{
                print("This add the merged video to the front of the mp4array")
                self.mp4Array.remove(at: 1)
                self.mp4Array.removeFirst()
                self.videoInt = self.videoInt + 1
                self.mp4Array.append(self.mergeUrl!)
                self.mp4Array.bringToFront(item: self.mp4Array.last!)
            }

            if (self.mp4Array.count > 1){
                if self.mergeUrl != nil {
                    self.mergeVideos()
                }
            } else {
                var numberofvideosdeleted = 0
                while (numberofvideosdeleted < self.videoInt - 1){
                    do {
                        print("deleting")
                        let url = documentDirectory.appendingPathComponent("MergedVideos/mergeVideo\(numberofvideosdeleted).mp4")
                        try FileManager.default.removeItem(at: url)
                        numberofvideosdeleted = numberofvideosdeleted + 1
                    } catch {
                        print("Error removing videos")
                    }
                }

                self.deleteCurrentSegementsInFolder()
            }
        }
    }
}

I ended up using FFmpeg Mobile to concatenate the videos and it works really well. Takes around 1 minute to concatenate a 3GB movie file.

Link below to the cocoapod: https://github.com/tanersener/mobile-ffmpeg

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM