创建一个带有有趣表情反应的Swiftui视频流应用程序
#教程 #ios #swift #swiftui

就像Instagram Live一样,视频流对我们的日常生活变得至关重要,并帮助我们保持与他人的互动和联系。本文将帮助您使用Stream Video SDK的SwiftUi组件构建iOS实时活动streaming app

Live streaming

本文还教您有关使用iPhone,Real-Time Messaging Protocol (RTMP)使用obs Studio和HTTP Live Streaming (HLS)的相机提要发布和观看现场流的信息。



可以为其他用例定制所得的live streaming app,例如音频/视频通话和音频聊天室。我们的视频SDK依靠WebRTC和HLS向观众提供超低的latency和可靠的内容流。

先决条件

入门

尽管获得流开发人员帐户是可选的,可以完成本教程,但构建生产实时流媒体应用需要一个流帐户。请访问以下链接以注册您的免费流帐户,并获得帮助,以与我们的SDK进行实时流媒体,语音/视频通话和音频室应用程序。

最终项目

本教程的最终演示应用程序类似于Instagram LiveTikTok Live,由两个简单的屏幕组成,允许用户启动和停止实时流。

Final Xcode live streaming project

下图演示了正在使用的演示应用程序。要自己运行该应用以查看其外观和感觉,您可以在GitHub上下载Xcode/swiftui项目。

Demo SwiftUI live streaming app

主要特征

  • 比例数百万:所有实时流都在Stream's Global Edge Network上运行,该流提供了极好的可靠性,安全性,安全性和广播能力。
  • 低潜伏期流(<500ms):视频SDK的基础架构是用WebbRTC构建的,该基础架构有助于在不同带宽的所有受众中向所有受众提供安全且超低的延迟视频流。
  • iOS设备和RTMP广播:您可以轻松地从iPhone或RTMP广播并扩展到无限的参与者。
  • 后台模式:主持人(主机和共同主持人)可以在上线之前配置流的后台。
  • HTTP Live Streaming (HLS) :SDK无缝支持Apple的HLS广播协议。

基础实时视频流技术选项

  • hls :HTTP实时流媒体使用普通的Web服务器通过HTTP发送媒体(音频和视频)。它允许用户观看有关iOS,TVO和MACOS的实时流和视频。我们的SDK与HLS无缝集成。这将使人们能够在低带宽和较差的网络条件下以出色的缓冲及以10到20秒的延迟观看应用内实时活动。访问Apple文档,以了解有关HLS architecture的更多信息。
  • RTMP使用obs或wirecast Real-Time Messaging Protocol (RTMP)是一种流媒体技术,可以通过互联网发送音频和视频。我们的iOS视频SDK为RTMP提供了无间隙的支持。您可以使用任何支持RTMP的软件,例如OBS StudioTelestream’s Wirecast
  • webrtc :通过WebRTC集成,人们可以通过超低延迟牢固地观看您的现场流。

步骤1:从Swiftui项目开始

启动Xcode并创建一个新的Swiftui项目。按照您的意愿将项目命名。本文中的演示应用程序使用 amlive 作为产品名称。
该应用程序使用iOS设备的相机和麦克风来实现其功能。相机和麦克风被认为是受保护的用户资产。因此,该应用需要在启动时访问用户的权限。

  1. 在Xcode Project Navigator中选择主应用程序夹。
  2. 前往 info 选项卡。
  3. 用鼠标光标悬停在键下的任何项目上
  4. 单击“ +”按钮,然后滚动到隐私类别。
  5. 添加隐私 - 摄像机用法描述隐私 - 麦克风用法描述。您可以将值留为空。将其留为空将提示用户使用“ appName希望访问您的相机”和“ appName希望访问您的麦克风”的用户。时间。

有关在iOS上设置保护资产的私有信息的更多信息,请查看Setting Background Modes and Device Capability Privacies in iOS Apps

步骤2:安装iOS视频SDK

使iOS视频SDK在Xcode项目中可用。

  1. 单击文件 - > 添加软件包依赖项
  2. 在右上角的搜索栏中复制并粘贴https://github.com/GetStream/stream-video-swift
  3. 请按照接下来的几个说明安装SDK。

步骤3:如何从iOS设备广播实时视频

Image description

使用iOS设备的相机提要广播实时流涉及:

  • 设置视频SDK。
  • 创建一个呼叫对象。
  • 初始化音频和视频的实时运输。

设置视频SDK
设置实现应在life cycle management发生的应用程序的位置进行。对于Uikit应用程序,可以在 appdelegate.swift 中完成设置。由于SwiftUi未实现AppDelegate,因此我们应该在应用程序的构象符号文件(主应用程序文件)中设置视频SDK amliveapp.swift.swift @main指令下方。

从:

开始
  1. 添加必要的导入。
import SwiftUI 
import StreamVideo
  1. 创建streamVideo客户端@State var streamVideo: StreamVideo的实例。
  2. 定义用户属性并创建一个用户对象
               let userToken = "REPLACE_WITH_TOKEN"
               let userId = "REPLACE_WITH_USER_ID"
               let callId = "REPLACE_WITH_CALL_ID"

               let user = User(id: userId, name: "tutorial")
  1. 使用API​​键和令牌初始化视频客户端
let streamVideo = StreamVideo(
            apiKey: "hd8szvscpxvd",
            user: user,
            token: .init(rawValue: userToken)
        )
        self.streamVideo = streamVideo

在实际生产iOS实时流媒体应用中,您必须从服务器生成用户令牌。您也可以使用我们的token generator service进行测试。当您的sign up for a Stream account时,您可以从dashboard中找到API键。

  1. 最后,在创建用户对象并初始化视频客户端之后,我们通过将callType指定为livestream并使用唯一的callId来创建并连接一个使用call对象的实时流。
let call = streamVideo.call(callType: "livestream", callId: callId)
    self.call = call
    Task {
      try await call.join(create: true)
    }

在上面的代码段中,call.join(create: true)创建,加入呼叫,并初始化音频和视频的实时传输。

将上述所有步骤放在一起

import SwiftUI
import StreamVideo

@main
// MARK:  Broadcast a Live Video From an iOS Device
struct AmLiveApp: App {

    // Create an instance of the StreamVideo client
    @State var streamVideo: StreamVideo
    let call: Call

    init() {
        // Define user properties
        let userToken = "REPLACE_WITH_TOKEN"
        let userId = "REPLACE_WITH_USER_ID"
        let callId = "REPLACE_WITH_CALL_ID"

        // Create a user object
        let user = User(id: userId, name: "tutorial")

        // Initialize the video client with the API key and token

        let streamVideo = StreamVideo(
            apiKey: "YOUR_API_KEY",
            user: user,
            token: .init(rawValue: userToken)
        )
        self.streamVideo = streamVideo

        // Create and join a live stream
        let call = streamVideo.call(callType: "livestream", callId: callId)
        self.call = call
        Task {
            try await call.join(create: true)
        }
    }

    var body: some Scene {
        WindowGroup {
            LiveStreamView(call: call)
        }
    }
}

struct LiveStreamView: View {

    let call: Call

    var body: some View {
        VStack(spacing: 120) {
            Text("You are now broadcasting from your phone")
            LivestreamingCharacter()
        }
    }

}

注释:在运行应用程序之前,应在我们的文档的live streaming教程中替换所有以下属性值。

        let userToken = "REPLACE_WITH_TOKEN"
        let userId = "REPLACE_WITH_USER_ID"
        let callId = "REPLACE_WITH_CALL_ID"
let streamVideo = StreamVideo(
            apiKey: "YOUR_API_KEY",
            user: user,
            token: .init(rawValue: userToken)
        )
        self.streamVideo = streamVideo

生成的凭据将与下图相似。

Image description

应用程序成功运行时,您将看到一个类似于下图的屏幕。屏幕上的表示形式意味着您现在正在使用iOS设备(iPhone或iPad)进行广播。

Image description

步骤5:如何渲染和观看主持人的视频

本文的演示应用程序具有与Facebook Live相似的界面。它只有两个屏幕。启动实时视频的屏幕进行直播和一个屏幕以结束实时流停止Lifestream

Image description

我们将通过幻想和令人难忘的SwiftUI animations来增强观看体验,以使活动和活动令人愉悦,并吸引最终用户。当实时流开始时,观众可以发送带有文本和表情符号的实时评论。

当您从github的download the final SwiftUI project时,您会在Xcode Project Navigator中的文件夹 Reactions 中找到所有动画。我们将将所有Swiftui动画添加到实时观看视图中,但不会潜入创建它们。在另一篇文章中,请查看SwiftUI Animation: How To Add Facebook Live Reactions to Livestream Chats以了解更多信息。

现场流的工作方式
当用户点击Go Live按钮启动实时视频时,我们的后端SDK使用名为Selective Forwarding Unit (SFU)的媒体服务器来复制全球不同SFU的实时内容。 SFU技术与点对点网络不同,允许流媒体可靠,可靠地将其实时活动缩放到数百万的观众实时。

呼叫状态和参与者
在活动的直播过程中,我们需要访问有关流的信息,并向查看流的人们显示。 SDK的call对象允许访问有关后台,直播持续时间,观看参与者的数量以及参与者的描述的信息。

  • call.state.backstage:确定是否启用了后台。
  • call.state.duration:确定活动流的总时间。
  • call.state.participantCount:显示观看直播的人数。
  • call.state.participants:参与者的列表和描述。

请参阅我们的文档的Call & Participant State部分以了解更多信息。

渲染主机的视频

我们利用SDK的视频渲染器显示当地参与者的视频(主机)。这些步骤遵循上一节中概述的过程,如何从iOS设备中广播实时视频。总结:

  1. 设置用户对象。
  2. 创建并初始化流视频客户端。
  3. 创建一个呼叫并将类型指定为具有唯一callIdlivestream
  4. 加入通话并启动音频和视频的实时运输

让我们更新 amliveapp.swift 的内容。

//
//  AmLiveApp.swift

import SwiftUI
import StreamVideo

// MARK: Video Rendering
struct AmLiveApp: App {

    @State var streamVideo: StreamVideo
    let call: Call

    init() {
        let userToken = "REPLACE_WITH_TOKEN"
        let userId = "REPLACE_WITH_USER_ID"
        let callId = "REPLACE_WITH_CALL_ID"

        // Set up the user
        let user = User(id: userId, name: "tutorial")

        // Create the Stream Video client
        let streamVideo = StreamVideo(
           apiKey: "YOUR_API_KEY",
            user: user,
            token: .init(rawValue: userToken)
        )
        self.streamVideo = streamVideo

        // Create a call: Specify the type as live stream with a unique callID
        let call = streamVideo.call(callType: "livestream", callId: callId)
        self.call = call
        Task {
            // Create the call object on our server and initiate the real-time transport for audio and video
            try await call.join(create: true)
        }
    }

    var body: some Scene {
        WindowGroup {
            LivestreamView(call: call)
        }
    }
}

上面的代码呈现主机的视频。但是,我们需要屏幕来显示来自相机供稿,通话状态和参与者的实时视频。 UI只不过是标准的SwiftUi视图。让我们下一个创建。

创建直播UIS
在Xcode Project Navigator中单击“单击应用程序”文件夹,然后添加一个新的Swift文件。让我们称其为 liveStreamView 或使用您喜欢的任何名称。用下面的示例代码替换 liveStreamView.swift

//
//  LivestreamView.swift

import SwiftUI
import StreamVideo
import StreamVideoSwiftUI

struct LivestreamView: View {

    @Injected(\.streamVideo) var streamVideo

    let call: Call

    // Handles backstage, duration, number of participants , list of participants
    @StateObject var state: CallState

    let formatter = DateComponentsFormatter()

    init(call: Call) {
        self.call = call
        _state = StateObject(wrappedValue: call.state)
        formatter.unitsStyle = .positional
    }

    var duration: String? {
        guard call.state.duration > 0  else { return nil }
        return formatter.string(from: call.state.duration)
    }

    var body: some View {
        NavigationStack {
            ZStack {
                GeometryReader { reader in
                    if let first = state.participants.first {
                        VideoRendererView(id: first.id, size: reader.size) { renderer in
                            renderer.handleViewRendering(for: first) { size, participant in }
                        }
                    } else {
                        Color(UIColor.secondarySystemBackground)
                    }
                }

                VStack {

                    if call.state.backstage {
                        Button {
                            Task {
                                try await call.goLive()
                                //try await call.startHLS()
                                /*try await call.goLive(
                                        startHls: true,
                                        startRecording: true,
                                        startTranscription: true
                                    )*/
                            }
                        } label: {
                            Text("Go Live")
                                .bold()
                                .padding(EdgeInsets(top: 12, leading: 32, bottom: 12, trailing: 32))
                        }
                        .buttonStyle(.borderedProminent)
                        .padding(.bottom, 24)

                    } else {
                        //Spacer()
                        CommentView()
                            .padding(.bottom, 64)
                        Button(role: .destructive) {
                            Task {
                                try await call.stopLive()
                            }
                        } label: {
                            Text("Stop Livestream")
                                .bold()
                                .padding(EdgeInsets(top: 12, leading: 32, bottom: 12, trailing: 32))
                        }
                        .buttonStyle(.borderedProminent)
                        .padding(.bottom, 24)

                        ReactionsView()
                    }


                }
                .padding(.bottom, 32)
            }
            .ignoresSafeArea()
            .toolbar {
                ToolbarItem(placement: .topBarLeading){
                    if let duration {
                        Text("Live \(duration)")
                            .font(.headline)
                            .bold()
                            .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
                            .background(.quaternary)
                            .cornerRadius(8)
                    }
                }

                ToolbarItemGroup(placement: .topBarTrailing){
                    HStack {
                        Image(systemName: "eyes.inverse")
                            .symbolEffect(.pulse)
                            .bold()
                        Text("Watching \(state.participantCount)")
                            .bold()
                            .opacity(call.state.backstage ? 0 : 1)
                    }
                    .padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8))
                    .background(.quaternary)
                    .cornerRadius(8)
                }
            }
        }
    }

}

查看上述代码的摘要,我们将后台的UI和Active Live流的UI放在NavigationStack中。在后台屏幕上,我们显示一个按钮进行实时启动现场视频。实时视频屏幕显示了持续时间和观看流中心和尾随工具栏中的流的人数。

停止直播按钮是CommentView.swift的评论视图。反应栏和所有动画来自Xcode Project Navigator中的 Reactions 文件夹中的ReactionsView.swift

当您在iPhone上运行该应用程序时,结果将类似于下面的图像。您可以通过点击进行现场直播按钮来启动实时视频。在直播屏幕上,您可以发表评论。

注意:它仅用于演示。对于实际应用程序,您应该实现注释以允许用户参与实时流。

Image description

添加随机用户观看实时流

在上面的视频中,您会在右上角注意到只有一个人正在观看现场流。使用流视频Companion Web应用程序getstream.io/video/demos,您可以添加查看观众以观看实时流。为此,您应该使用具有相同callID的随机用户。使用我们的随机用户生成器generator.getstream.io,使用多个随机用户观看直播流。

下图显示了15个人观看直播的示例。

Multiple people watching a live stream

使用HLS

观看直播流

人们可以观看已经开始使用视频SDK的HTTP实时流(HLS)支持的流。 HLS具有比WEBRTC更好缓冲的优点。但是,它的延迟延迟了10到20秒。让我们更新 amliveapp.swift 中的Task闭合以使用HLS观看实时流。

// MARK: Viewing the livestream with HLS
        Task {
            try await call.startHLS()
            if let hls = call.state.egress?.hls?.playlistUrl {
                print("hls url = \(hls)")
            }
        }

步骤6:使用obs发行RTMP

Image description

在上一节中,您发现了如何使用iOS设备(iPhone)的相机提要发布实时流。视频SDK提供了最大的灵活性,可以使用您的首选设备实时流。本节将通过OBS Studio与WEBRTC(RTMP)发布实时视频。随意使用支持RTMP的您喜欢的流媒体软件。通过obs发布实时流,需要使用服务器或RTMP URL 流键键

进行以下步骤。
  1. 通过在Xcode控制台中打印它们,获取服务器URL (RTMP)和流键。在 amliveapp.swift 中,我们有Task闭合:

    Task {
                try await call.join(create: true)
        }
    

让我们将其更新为:

Task {
            try await call.join(create: true)
            if let rtmp = call.state.ingress?.rtmp {
                let address = rtmp.address
                let streamKey = rtmp.streamKey
                print("RTMP url \(address) and streamingKey \(streamKey)")
            }
        }

上面的代码在Xcode Console中打印 RTMP URL 流键

Image description

  1. 启动OBS Studio。从窗口的右下角单击设置
  2. 从左菜单项中选择
  3. 复制并粘贴 rtmp url 流键从Xcode控制台。

Image description

  1. 单击应用 ok 以确认更改。
  2. 接下来,在来源下指定您的输入视频,然后单击在OBS的右下角开始流式传输以观看实时视频。

Image description

hooray !!!。下面的视频显示了实时流的示例。

Image description

下一步是什么?

本教程教您如何使用SwiftUi创建应用程序内iOS实时流媒体应用程序。我们希望在iOS设备,支持RTMP和HLS的视频流软件(OBS)上观看广播和观看直播。

我们介绍了将视频流添加到您的iOS/swiftui应用中的基本原理。要超越基本知识,请查看我们的视频SDK支持的高级功能,例如custom eventsrecordingchatnotifications,以了解更多信息。