我在应用程序中使用了agora的视频广播SDK来实现广播功能。但是在切换应用程序时崩溃了。 错误:线程24:EXC_BAD_ACCESS(代码= 1,地址= 0x1250f0000)
以下是我的视频广播代码。
import UIKit
import AgoraRtcKit
import SendBirdSDK
protocol LiveVCDataSource: NSObjectProtocol {
func liveVCNeedAgoraKit() -> AgoraRtcEngineKit
func liveVCNeedSettings() -> Settings
}
//LiveComment
@objc protocol CreateOpenChannelDelegate: NSObjectProtocol {
@objc optional func didCreate(_ channel: SBDOpenChannel)
}
@objc protocol OpenChanannelChatDelegate: NSObjectProtocol {
@objc optional func didUpdateOpenChannel()
}
class LiveRoomViewController: UIViewController {
var name = ""
var imgString = ""
@IBOutlet weak var img_user: UIImageView!
@IBOutlet weak var lbl_name: UILabel!
@IBOutlet weak var broadcastersView: AGEVideoContainer!
@IBOutlet weak var placeholderView: UIImageView!
@IBOutlet weak var videoMuteButton: UIButton!
@IBOutlet weak var audioMuteButton: UIButton!
@IBOutlet weak var beautyEffectButton: UIButton!
@IBOutlet var sessionButtons: [UIButton]!
private let beautyOptions: AgoraBeautyOptions = {
let options = AgoraBeautyOptions()
options.lighteningContrastLevel = .normal
options.lighteningLevel = 0.7
options.smoothnessLevel = 0.5
options.rednessLevel = 0.1
return options
}()
private var agoraKit: AgoraRtcEngineKit {
return dataSource!.liveVCNeedAgoraKit()
}
private var settings: Settings {
return dataSource!.liveVCNeedSettings()
}
private var isMutedVideo = false {
didSet {
// mute local video
agoraKit.muteLocalVideoStream(isMutedVideo)
videoMuteButton.isSelected = isMutedVideo
}
}
private var isMutedAudio = false {
didSet {
// mute local audio
agoraKit.muteLocalAudioStream(isMutedAudio)
audioMuteButton.isSelected = isMutedAudio
}
}
private var isBeautyOn = false {
didSet {
// improve local render view
agoraKit.setBeautyEffectOptions(isBeautyOn,
options: isBeautyOn ? beautyOptions : nil)
beautyEffectButton.isSelected = isBeautyOn
}
}
private var isSwitchCamera = false {
didSet {
agoraKit.switchCamera()
}
}
private var videoSessions = [VideoSession]() {
didSet {
placeholderView.isHidden = (videoSessions.count == 0 ? false : true)
// update render view layout
updateBroadcastersView()
}
}
private let maxVideoSession = 4
weak var dataSource: LiveVCDataSource?
var isOffline:Bool = true
//LiveComment
@IBOutlet weak var inputMessageTextField: UITextField!
@IBOutlet weak var messageTableView: UITableView!
@IBOutlet weak var inputMessageInnerContainerViewBottomMargin: NSLayoutConstraint!
@IBOutlet weak var txtMsgView: UIView!
@IBOutlet weak var inputMsgViewHeight: NSLayoutConstraint!
@IBOutlet weak var sendUserMessageButton: UIButton!
@IBOutlet weak var sendFileMessageButton: UIButton!
weak var delegate: OpenChanannelChatDelegate?
weak var createChannelDelegate: CreateOpenChannelDelegate?
var channels: SBDOpenChannel?
var keyboardShown: Bool = false
var keyboardHeight: CGFloat = 0
var firstKeyboardShown: Bool = true
var initialLoading: Bool = true
var stopMeasuringVelocity: Bool = false
var lastMessageHeight: CGFloat = 0
var scrollLock: Bool = false
var lastOffset: CGPoint = CGPoint(x: 0, y: 0)
var lastOffsetCapture: TimeInterval = 0
var isScrollingFast: Bool = false
var hasPrevious: Bool?
var minMessageTimestamp: Int64 = Int64.max
var isLoading: Bool = false
var messages: [SBDBaseMessage] = []
var resendableMessages: [String:SBDBaseMessage] = [:]
var preSendMessages: [String:SBDBaseMessage] = [:]
var preSendFileData: [String:[String:AnyObject]] = [:]
var resendableFileData: [String:[String:AnyObject]] = [:]
var fileTransferProgress: [String:CGFloat] = [:]
var selectedMessage: SBDBaseMessage?
var channelUpdated: Bool = false
var sendingImageVideoMessage: [String: Bool] = [:]
var loadedImageHash: [String:Int] = [:]
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(true, animated: false)
self.navigationItem.setHidesBackButton(true, animated:false);
updateButtonsVisiablity()
print(settings.role)
if settings.role == .broadcaster {
txtMsgView.isHidden = true
inputMsgViewHeight.constant = 0
} else {
txtMsgView.isHidden = false
inputMsgViewHeight.constant = 89
}
viewDidLoadAgain()
self.lbl_name.text = (name.isEmpty) ? UserDefaults.standard.string(forKey: "isMeUserName") : name
self.img_user.loadImage(strUrl: (imgString.isEmpty) ? UserDefaults.standard.string(forKey: "isMeUserAvatar") : imgString)
loadAgoraKit()
}
//MARK: Action Methods
@objc private func backButtonAction() {
// self.navigationController?.popViewController(animated: true)
self.dismiss(animated: true, completion: nil)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
//MARK: - ui action
@IBAction func doSwitchCameraPressed(_ sender: UIButton) {
DispatchQueue.global(qos: .background).async {
self.isSwitchCamera = !self.isSwitchCamera
}
}
@IBAction func doBeautyPressed(_ sender: UIButton) {
isBeautyOn.toggle()
}
@IBAction func doMuteVideoPressed(_ sender: UIButton) {
isMutedVideo.toggle()
}
@IBAction func doMuteAudioPressed(_ sender: UIButton) {
isMutedAudio.toggle()
}
@IBAction func doLeavePressed(_ sender: UIButton) {
APIManager.shared.request(endPoint: .live,
method: .delete,
params: nil) { (result: Result<Success<[String: String]>, Error>) in
switch result {
case .success(let response):
if let error = response.data?["error"] {
error.toast(.error)
return
}
self.leaveChannel()
case .failure(let error):
error.localizedDescription.toast(.error)
}
}
//leaveChannel()
}
func actionSheet() {
let refreshAlert = UIAlertController(title: "Alert", message: "User is not live.", preferredStyle: .actionSheet)
refreshAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { (action: UIAlertAction!) in
self.leaveChannel()
}))
present(refreshAlert, animated: true, completion: nil)
}
}
private extension LiveRoomViewController {
func updateBroadcastersView() {
// video views layout
if videoSessions.count == maxVideoSession {
broadcastersView.reload(level: 0, animated: true)
} else {
var rank: Int
var row: Int
if videoSessions.count == 0 {
broadcastersView.removeLayout(level: 0)
return
} else if videoSessions.count == 1 {
rank = 1
row = 1
} else if videoSessions.count == 2 {
rank = 1
row = 2
} else {
rank = 2
row = Int(ceil(Double(videoSessions.count) / Double(rank)))
}
let itemWidth = CGFloat(1.0) / CGFloat(rank)
let itemHeight = CGFloat(1.0) / CGFloat(row)
let itemSize = CGSize(width: itemWidth, height: itemHeight)
let layout = AGEVideoLayout(level: 0)
.itemSize(.scale(itemSize))
broadcastersView
.listCount { [unowned self] (_) -> Int in
return self.videoSessions.count
}.listItem { [unowned self] (index) -> UIView in
return self.videoSessions[index.item].hostingView
}
broadcastersView.setLayouts([layout], animated: true)
}
}
func updateButtonsVisiablity() {
guard let sessionButtons = sessionButtons else {
return
}
let isHidden = settings.role == .audience
for item in sessionButtons {
item.isHidden = isHidden
}
}
func setIdleTimerActive(_ active: Bool) {
UIApplication.shared.isIdleTimerDisabled = !active
}
}
private extension LiveRoomViewController {
func getSession(of uid: UInt) -> VideoSession? {
for session in videoSessions {
if session.uid == uid {
return session
}
}
return nil
}
func videoSession(of uid: UInt) -> VideoSession {
if let fetchedSession = getSession(of: uid) {
return fetchedSession
} else {
let newSession = VideoSession(uid: uid)
videoSessions.append(newSession)
return newSession
}
}
}
//MARK: - Agora Media SDK
private extension LiveRoomViewController {
func loadAgoraKit() {
guard let channelId = settings.roomName else {
return
}
setIdleTimerActive(false)
// Step 1, set delegate to inform the app on AgoraRtcEngineKit events
agoraKit.delegate = self
// Step 2, set live broadcasting mode
// for details: https://docs.agora.io/cn/Video/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/setChannelProfile:
agoraKit.setChannelProfile(.liveBroadcasting)
// set client role
agoraKit.setClientRole(settings.role)
// Step 3, Warning: only enable dual stream mode if there will be more than one broadcaster in the channel
agoraKit.enableDualStreamMode(true)
// Step 4, enable the video module
agoraKit.enableVideo()
// set video configuration
agoraKit.setVideoEncoderConfiguration(
AgoraVideoEncoderConfiguration(
size: settings.dimension,
frameRate: settings.frameRate,
bitrate: AgoraVideoBitrateStandard,
orientationMode: .adaptative
)
)
// if current role is broadcaster, add local render view and start preview
if settings.role == .broadcaster {
addLocalSession()
agoraKit.startPreview()
}
// Step 5, join channel and start group chat
// If join channel success, agoraKit triggers it's delegate function
// 'rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int)'
agoraKit.joinChannel(byToken: KeyCenter.Token, channelId: channelId, info: nil, uid: 0, joinSuccess: nil)
// Step 6, set speaker audio route
agoraKit.setEnableSpeakerphone(true)
}
func addLocalSession() {
let localSession = VideoSession.localSession()
localSession.updateInfo(fps: settings.frameRate.rawValue)
videoSessions.append(localSession)
agoraKit.setupLocalVideo(localSession.canvas)
}
func leaveChannel() {
// Step 1, release local AgoraRtcVideoCanvas instance
agoraKit.setupLocalVideo(nil)
// Step 2, leave channel and end group chat
agoraKit.leaveChannel(nil)
// Step 3, if current role is broadcaster, stop preview after leave channel
if settings.role == .broadcaster {
agoraKit.stopPreview()
}
setIdleTimerActive(true)
navigationController?.popViewController(animated: true)
}
}
// MARK: - AgoraRtcEngineDelegate
extension LiveRoomViewController: AgoraRtcEngineDelegate {
/// Occurs when the first local video frame is displayed/rendered on the local video view.
///
/// Same as [firstLocalVideoFrameBlock]([AgoraRtcEngineKit firstLocalVideoFrameBlock:]).
/// @param engine AgoraRtcEngineKit object.
/// @param size Size of the first local video frame (width and height).
/// @param elapsed Time elapsed (ms) from the local user calling the [joinChannelByToken]([AgoraRtcEngineKit joinChannelByToken:channelId:info:uid:joinSuccess:]) method until the SDK calls this callback.
///
/// If the [startPreview]([AgoraRtcEngineKit startPreview]) method is called before the [joinChannelByToken]([AgoraRtcEngineKit joinChannelByToken:channelId:info:uid:joinSuccess:]) method, then `elapsed` is the time elapsed from calling the [startPreview]([AgoraRtcEngineKit startPreview]) method until the SDK triggers this callback.
func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalVideoFrameWith size: CGSize, elapsed: Int) {
if let selfSession = videoSessions.first {
selfSession.updateInfo(resolution: size)
}
}
/// Reports the statistics of the current call. The SDK triggers this callback once every two seconds after the user joins the channel.
func rtcEngine(_ engine: AgoraRtcEngineKit, reportRtcStats stats: AgoraChannelStats) {
if let selfSession = videoSessions.first {
selfSession.updateChannelStats(stats)
} else {
isOffline = false
if isOffline {
kb.topMostVC?.startAnimateLoader()
isOffline = false
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: {
kb.topMostVC?.stopAnimating()
self.actionSheet()
})
}
}
}
/// Occurs when the first remote video frame is received and decoded.
/// - Parameters:
/// - engine: AgoraRtcEngineKit object.
/// - uid: User ID of the remote user sending the video stream.
/// - size: Size of the video frame (width and height).
/// - elapsed: Time elapsed (ms) from the local user calling the joinChannelByToken method until the SDK triggers this callback.
func rtcEngine(_ engine: AgoraRtcEngineKit, firstRemoteVideoDecodedOfUid uid: UInt, size: CGSize, elapsed: Int) {
guard videoSessions.count <= maxVideoSession else {
return
}
let userSession = videoSession(of: uid)
userSession.updateInfo(resolution: size)
agoraKit.setupRemoteVideo(userSession.canvas)
}
/// Occurs when a remote user (Communication)/host (Live Broadcast) leaves a channel. Same as [userOfflineBlock]([AgoraRtcEngineKit userOfflineBlock:]).
///
/// There are two reasons for users to be offline:
///
/// - Leave a channel: When the user/host leaves a channel, the user/host sends a goodbye message. When the message is received, the SDK assumes that the user/host leaves a channel.
/// - Drop offline: When no data packet of the user or host is received for a certain period of time (20 seconds for the Communication profile, and more for the Live-broadcast profile), the SDK assumes that the user/host drops offline. Unreliable network connections may lead to false detections, so Agora recommends using a signaling system for more reliable offline detection.
///
/// @param engine AgoraRtcEngineKit object.
/// @param uid ID of the user or host who leaves a channel or goes offline.
/// @param reason Reason why the user goes offline, see AgoraUserOfflineReason.
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
var indexToDelete: Int?
for (index, session) in videoSessions.enumerated() where session.uid == uid {
indexToDelete = index
break
}
if let indexToDelete = indexToDelete {
let deletedSession = videoSessions.remove(at: indexToDelete)
deletedSession.hostingView.removeFromSuperview()
// release canvas's view
deletedSession.canvas.view = nil
}
}
/// Reports the statistics of the video stream from each remote user/host.
func rtcEngine(_ engine: AgoraRtcEngineKit, remoteVideoStats stats: AgoraRtcRemoteVideoStats) {
if let session = getSession(of: stats.uid) {
session.updateVideoStats(stats)
}
}
/// Reports the statistics of the audio stream from each remote user/host.
func rtcEngine(_ engine: AgoraRtcEngineKit, remoteAudioStats stats: AgoraRtcRemoteAudioStats) {
if let session = getSession(of: stats.uid) {
session.updateAudioStats(stats)
}
}
/// Reports a warning during SDK runtime.
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) {
print("warning code: \(warningCode.description)")
}
/// Reports an error during SDK runtime.
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
print("error code: \(errorCode.description)")
}
}
如果我做错了什么或有任何建议如何解决此问题,请帮助我。预先感谢。
答案 0 :(得分:0)
每当您访问agoraKit
时,您实际上就是在呼叫它:
dataSource!.liveVCNeedAgoraKit()
这涉及强制展开弱的可选变量。如果dataSource
为nil
,则将看到错误。错误还可能出在liveVCNeedAgoraKit()
函数中,该函数可能正在执行类似的强制展开。
您不应该对代表/数据源使用强制解包,这可能没有设置并且随时可能消失。使用可选的绑定,如果未设置委托,则进行一些回退。
if let source = dataSource {
return source.liveVCNeedAgoraKit()
} else {
// Error handling. Display an alert, return nil, initialize a new agoraKit to return - whatever makes the most sense for your app.
}