Nic*_*aft 5 audio record stream ios swift
我正在为 iPhone 创建一个收音机应用程序(用 Swift 4.2 编码),我想添加一个功能,允许我记录并保存在文件中,当我按下按钮时收音机产生的声音(从 AVPlayer 读取)。我应该使用哪个代码?
该代码使用 Swift 4.2 和 Xcode 10.1。我在网上搜索:“如何录制音频流 swift 4.2”,“如何从 AVPlayer swift 4.2 录制音频”,但我找不到答案。
我的代码:
import UIKit
import AVFoundation
import MediaPlayer
class ViewControllerPlayer: UIViewController {
var URl = "http://link_of_audio_stream"
var player:AVPlayer?
var playerItem:AVPlayerItem?
var playerLayer:AVPlayerLayer?
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: URl)
let playerItem1:AVPlayerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem1)
}
@IBAction func Play(_ sender: Any) {
player?.play()
}
@IBAction func Pause(_ sender: Any) {
player?.pause()
}
private var audioRecorder: AVAudioRecorder!
func startRecording() throws {
guard let newFileURL = createURLForNewRecord() else {
throw RecordingServiceError.canNotCreatePath
}
do {
var urlString = URL(string: URl)
urlString = newFileURL
audioRecorder = try AVAudioRecorder(url: newFileURL,
settings: [AVFormatIDKey:Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 8000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.min.rawValue])
audioRecorder.delegate = self as? AVAudioRecorderDelegate
audioRecorder.prepareToRecord()
audioRecorder.record(forDuration: TimeConstants.recordDuration)
//error: Use of unresolved identifier 'TimeConstants'
} catch let error {
print(error)
}
}
func STOPREC1() throws {
audioRecorder.stop()
audioRecorder = nil
print("Recording finished successfully.")
}
enum RecordingServiceError: String, Error {
case canNotCreatePath = "Can not create path for new recording"
}
private func createURLForNewRecord() -> URL? {
guard let appGroupFolderUrl = FileManager.getAppFolderURL() else {
return nil
}
let date = String(describing: Date())
let fullFileName = "Enregistrement radio " + date + ".m4a"
let newRecordFileName = appGroupFolderUrl.appendingPathComponent(fullFileName)
return newRecordFileName
}
}
extension FileManager {
class func getAppFolderURL() -> URL? {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
}
Run Code Online (Sandbox Code Playgroud)
经过多次网上搜索,我找到了解决方案。
\n我在互联网上的某个地方发现了这个名为 \xc2\xab\xc2\xa0CachingPlayerItem.swift\xc2\xa0\xc2\xbb 的 Swift 类,它将允许录制在线音频流。
\nimport Foundation\nimport AVFoundation\n\nfileprivate extension URL {\n \n func withScheme(_ scheme: String) -> URL? {\n var components = URLComponents(url: self, resolvingAgainstBaseURL: false)\n components?.scheme = scheme\n return components?.url\n }\n \n}\n\n@objc protocol CachingPlayerItemDelegate {\n \n /// Is called when the media file is fully downloaded.\n @objc optional func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data)\n \n /// Is called every time a new portion of data is received.\n @objc optional func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int)\n \n /// Is called after initial prebuffering is finished, means\n /// we are ready to play.\n @objc optional func playerItemReadyToPlay(_ playerItem: CachingPlayerItem)\n \n /// Is called when the data being downloaded did not arrive in time to\n /// continue playback.\n @objc optional func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem)\n \n /// Is called on downloading error.\n @objc optional func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error)\n \n}\n\nopen class CachingPlayerItem: AVPlayerItem {\n \n class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {\n \n var playingFromData = false\n var mimeType: String? // is required when playing from Data\n var session: URLSession?\n var mediaData: Data?\n var response: URLResponse?\n var pendingRequests = Set<AVAssetResourceLoadingRequest>()\n weak var owner: CachingPlayerItem?\n var fileURL: URL!\n var outputStream: OutputStream?\n \n func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {\n \n if playingFromData {\n \n // Nothing to load.\n \n } else if session == nil {\n \n // If we're playing from a url, we need to download the file.\n // We start loading the file on first request only.\n guard let initialUrl = owner?.url else {\n fatalError("internal inconsistency")\n }\n\n startDataRequest(with: initialUrl)\n }\n \n pendingRequests.insert(loadingRequest)\n processPendingRequests()\n return true\n \n }\n \n func startDataRequest(with url: URL) {\n \n var recordingName = "record.mp3"\n if let recording = owner?.recordingName{\n recordingName = recording\n }\n \n fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)\n .appendingPathComponent(recordingName)\n let configuration = URLSessionConfiguration.default\n configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData\n session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)\n session?.dataTask(with: url).resume()\n outputStream = OutputStream(url: fileURL, append: true)\n outputStream?.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)\n outputStream?.open()\n \n }\n \n func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {\n pendingRequests.remove(loadingRequest)\n }\n \n // MARK: URLSession delegate\n \n func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {\n let bytesWritten = data.withUnsafeBytes{outputStream?.write($0, maxLength: data.count)}\n }\n \n func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {\n completionHandler(Foundation.URLSession.ResponseDisposition.allow)\n mediaData = Data()\n self.response = response\n processPendingRequests()\n }\n \n func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {\n if let errorUnwrapped = error {\n owner?.delegate?.playerItem?(owner!, downloadingFailedWith: errorUnwrapped)\n return\n }\n processPendingRequests()\n owner?.delegate?.playerItem?(owner!, didFinishDownloadingData: mediaData!)\n }\n \n // MARK: -\n \n func processPendingRequests() {\n \n // get all fullfilled requests\n let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(pendingRequests.compactMap {\n self.fillInContentInformationRequest($0.contentInformationRequest)\n if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {\n $0.finishLoading()\n return $0\n }\n return nil\n })\n \n // remove fulfilled requests from pending requests\n _ = requestsFulfilled.map { self.pendingRequests.remove($0) }\n\n }\n \n func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) {\n if playingFromData {\n contentInformationRequest?.contentType = self.mimeType\n contentInformationRequest?.contentLength = Int64(mediaData!.count)\n contentInformationRequest?.isByteRangeAccessSupported = true\n return\n }\n \n guard let responseUnwrapped = response else {\n // have no response from the server yet\n return\n }\n \n contentInformationRequest?.contentType = responseUnwrapped.mimeType\n contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength\n contentInformationRequest?.isByteRangeAccessSupported = true\n \n }\n \n func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {\n \n let requestedOffset = Int(dataRequest.requestedOffset)\n let requestedLength = dataRequest.requestedLength\n let currentOffset = Int(dataRequest.currentOffset)\n \n guard let songDataUnwrapped = mediaData,\n songDataUnwrapped.count > currentOffset else {\n return false\n }\n \n let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength)\n let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))\n dataRequest.respond(with: dataToRespond)\n \n return songDataUnwrapped.count >= requestedLength + requestedOffset\n \n }\n \n deinit {\n session?.invalidateAndCancel()\n }\n \n }\n \n fileprivate let resourceLoaderDelegate = ResourceLoaderDelegate()\n fileprivate let url: URL\n fileprivate let initialScheme: String?\n fileprivate var customFileExtension: String?\n \n \n weak var delegate: CachingPlayerItemDelegate?\n \n func stopDownloading(){\n resourceLoaderDelegate.session?.invalidateAndCancel()\n }\n \n open func download() {\n if resourceLoaderDelegate.session == nil {\n resourceLoaderDelegate.startDataRequest(with: url)\n }\n }\n \n private let cachingPlayerItemScheme = "cachingPlayerItemScheme"\n var recordingName = "record.mp3"\n /// Is used for playing remote files.\n convenience init(url: URL, recordingName: String) {\n self.init(url: url, customFileExtension: nil, recordingName: recordingName)\n }\n \n /// Override/append custom file extension to URL path.\n /// This is required for the player to work correctly with the intended file type.\n init(url: URL, customFileExtension: String?, recordingName: String) {\n \n guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),\n let scheme = components.scheme,\n var urlWithCustomScheme = url.withScheme(cachingPlayerItemScheme) else {\n fatalError("Urls without a scheme are not supported")\n }\n self.recordingName = recordingName\n self.url = url\n self.initialScheme = scheme\n \n if let ext = customFileExtension {\n urlWithCustomScheme.deletePathExtension()\n urlWithCustomScheme.appendPathExtension(ext)\n self.customFileExtension = ext\n }\n \n let asset = AVURLAsset(url: urlWithCustomScheme)\n asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)\n super.init(asset: asset, automaticallyLoadedAssetKeys: nil)\n \n resourceLoaderDelegate.owner = self\n \n addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)\n \n NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)\n \n }\n \n /// Is used for playing from Data.\n init(data: Data, mimeType: String, fileExtension: String) {\n \n guard let fakeUrl = URL(string: cachingPlayerItemScheme + "://whatever/file.\\(fileExtension)") else {\n fatalError("internal inconsistency")\n }\n \n self.url = fakeUrl\n self.initialScheme = nil\n \n resourceLoaderDelegate.mediaData = data\n resourceLoaderDelegate.playingFromData = true\n resourceLoaderDelegate.mimeType = mimeType\n \n let asset = AVURLAsset(url: fakeUrl)\n asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)\n super.init(asset: asset, automaticallyLoadedAssetKeys: nil)\n resourceLoaderDelegate.owner = self\n \n addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)\n \n NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)\n \n }\n \n // MARK: KVO\n \n override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {\n delegate?.playerItemReadyToPlay?(self)\n }\n \n // MARK: Notification hanlers\n \n @objc func playbackStalledHandler() {\n delegate?.playerItemPlaybackStalled?(self)\n }\n\n // MARK: -\n \n override init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?) {\n fatalError("not implemented")\n }\n \n deinit {\n NotificationCenter.default.removeObserver(self)\n removeObserver(self, forKeyPath: "status")\n resourceLoaderDelegate.session?.invalidateAndCancel()\n }\n \n}\nRun Code Online (Sandbox Code Playgroud)\n之后,在您的主 swift 文件中,您将以下代码记录下来:
\nlet recordingName = "my_rec_name.mp3"\nvar playerItem: CachingPlayerItem!\nlet url_stream = URL(string: "http://my_url_stream_link")\nplayerItem = CachingPlayerItem(url: url_stream!, recordingName: recordingName ?? "record.mp3")\nvar player1 = AVPlayer(playerItem: playerItem)\nplayer1.automaticallyWaitsToMinimizeStalling = false\nRun Code Online (Sandbox Code Playgroud)\n要停止记录,请使用以下代码:
\nplayerItem.stopDownloading()\nrecordingName = nil\nplayerItem = nil\nRun Code Online (Sandbox Code Playgroud)\n录音将保存在您的应用程序的目录中。
\n