简体   繁体   English

切换摄像头时 Agora 视频广播崩溃

[英]Agora Video Broadcasting Crashing When switching camera

I used agora's video broadcasting SDK for broadcast functionality in my app.我在我的应用程序中使用 agora 的视频广播 SDK 来实现广播功能。 But at the time of switching app crashed.但是在切换应用程序时崩溃了。 Error: Thread 24: EXC_BAD_ACCESS (code=1, address=0x1250f0000)错误:线程 24:EXC_BAD_ACCESS(代码=1,地址=0x1250f0000)

I code for Video broadcasting is following.我的视频广播代码如下。

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)")
    }
}

Please help me out if I did something wrong in it or any suggestion how can I resolve this issue.如果我做错了什么或有任何建议如何解决此问题,请帮助我。 Thanks in advance.提前致谢。

Whenever you access agoraKit you're actually calling this:每当你访问agoraKit时,你实际上是在调用它:

dataSource!.liveVCNeedAgoraKit()

This involves force unwrapping a weak optional variable.这涉及强制展开弱可选变量。 If dataSource is nil , you'll get the error you're seeing.如果dataSourcenil ,您将得到您所看到的错误。 It's also possible that the error is in the liveVCNeedAgoraKit() function, which could be doing similar force unwrapping.错误也可能在liveVCNeedAgoraKit() function 中,它可能会执行类似的强制展开。

You shouldn't use force unwrapping with delegates/data sources, which might not be set and could go away at any time.您不应该对委托/数据源使用强制解包,它们可能未设置并且随时可能 go 消失。 Use optional binding instead, with some fallback if the delegate is not set.改用可选绑定,如果未设置委托,则使用一些回退。

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.
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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