diff --git a/BroadcastUploadExtension/Common.xcconfig b/BroadcastUploadExtension/Common.xcconfig new file mode 100644 index 000000000..f72e65ade --- /dev/null +++ b/BroadcastUploadExtension/Common.xcconfig @@ -0,0 +1,30 @@ +// +// Copyright 2023 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Config/AppIdentifiers.xcconfig" +#include "Config/AppVersion.xcconfig" + +PRODUCT_NAME = BroadcastUploadExtension +PRODUCT_BUNDLE_IDENTIFIER = $(BROADCAST_UPLOAD_EXTENSION_BUNDLE_IDENTIFIER) + +INFOPLIST_FILE = BroadcastUploadExtension/SupportingFiles/Info.plist + +CODE_SIGN_ENTITLEMENTS = BroadcastUploadExtension/SupportingFiles/BroadcastUploadExtension.entitlements + +SKIP_INSTALL = YES diff --git a/BroadcastUploadExtension/Debug.xcconfig b/BroadcastUploadExtension/Debug.xcconfig new file mode 100644 index 000000000..c8a3620c1 --- /dev/null +++ b/BroadcastUploadExtension/Debug.xcconfig @@ -0,0 +1,21 @@ +// +// Copyright 2020 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Common.xcconfig" +#include "Pods/Target Support Files/Pods-RiotPods-BroadcastUploadExtension/Pods-RiotPods-BroadcastUploadExtension.debug.xcconfig" diff --git a/BroadcastUploadExtension/Release.xcconfig b/BroadcastUploadExtension/Release.xcconfig new file mode 100644 index 000000000..7a39b7de0 --- /dev/null +++ b/BroadcastUploadExtension/Release.xcconfig @@ -0,0 +1,26 @@ +// +// Copyright 2020 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include "Common.xcconfig" +#include "Pods/Target Support Files/Pods-RiotPods-BroadcastUploadExtension/Pods-RiotPods-BroadcastUploadExtension.release.xcconfig" + +PROVISIONING_PROFILE = $(BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE) +PROVISIONING_PROFILE_SPECIFIER = $(BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER) + +COPY_PHASE_STRIP = NO diff --git a/BroadcastUploadExtension/Sources/Atomic.swift b/BroadcastUploadExtension/Sources/Atomic.swift new file mode 100644 index 000000000..897e66f65 --- /dev/null +++ b/BroadcastUploadExtension/Sources/Atomic.swift @@ -0,0 +1,41 @@ +// +// License from the original repository: +// https://github.com/jitsi/jitsi-meet-sdk-samples/blob/master/LICENSE + +// +// Atomic.swift +// Broadcast Extension +// +// Created by Maksym Shcheglov. +// https://www.onswiftwings.com/posts/atomic-property-wrapper/ +// + +import Foundation + +@propertyWrapper +struct Atomic { + + private var value: Value + private let lock = NSLock() + + init(wrappedValue value: Value) { + self.value = value + } + + var wrappedValue: Value { + get { load() } + set { store(newValue: newValue) } + } + + func load() -> Value { + lock.lock() + defer { lock.unlock() } + return value + } + + mutating func store(newValue: Value) { + lock.lock() + defer { lock.unlock() } + value = newValue + } +} diff --git a/BroadcastUploadExtension/Sources/DarwinNotificationCenter.swift b/BroadcastUploadExtension/Sources/DarwinNotificationCenter.swift new file mode 100644 index 000000000..283e467b6 --- /dev/null +++ b/BroadcastUploadExtension/Sources/DarwinNotificationCenter.swift @@ -0,0 +1,33 @@ +// +// License from the original repository: +// https://github.com/jitsi/jitsi-meet-sdk-samples/blob/master/LICENSE + +// +// DarwinNotificationCenter.swift +// Broadcast Extension +// +// Created by Alex-Dan Bumbu on 23/03/2021. +// Copyright © 2021 8x8, Inc. All rights reserved. +// + +import Foundation + +enum DarwinNotification: String { + case broadcastStarted = "iOS_BroadcastStarted" + case broadcastStopped = "iOS_BroadcastStopped" +} + +class DarwinNotificationCenter { + + static let shared = DarwinNotificationCenter() + + private let notificationCenter: CFNotificationCenter + + init() { + notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() + } + + func postNotification(_ name: DarwinNotification) { + CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(rawValue: name.rawValue as CFString), nil, nil, true) + } +} diff --git a/BroadcastUploadExtension/Sources/SampleHandler.swift b/BroadcastUploadExtension/Sources/SampleHandler.swift new file mode 100644 index 000000000..7fb5c796c --- /dev/null +++ b/BroadcastUploadExtension/Sources/SampleHandler.swift @@ -0,0 +1,125 @@ +// +// License from the original repository: +// https://github.com/jitsi/jitsi-meet-sdk-samples/blob/master/LICENSE +// +// SampleHandler.swift +// Broadcast Extension +// +// Created by Alex-Dan Bumbu on 04.06.2021. +// + +import ReplayKit + +import MatrixSDK + +private enum Constants { + // the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app. + static let appGroupIdentifier = BuildSettings.applicationGroupIdentifier +} + +class SampleHandler: RPBroadcastSampleHandler { + + private var clientConnection: SocketConnection? + private var uploader: SampleUploader? + + private var frameCount: Int = 0 + + private var socketFilePath: String { + let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier) + return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? "" + } + + override init() { + super.init() + setupLogger() + + if let connection = SocketConnection(filePath: socketFilePath) { + clientConnection = connection + setupConnection() + + uploader = SampleUploader(connection: connection) + } + } + + override func broadcastStarted(withSetupInfo setupInfo: [String: NSObject]?) { + // User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional. + frameCount = 0 + + DarwinNotificationCenter.shared.postNotification(.broadcastStarted) + openConnection() + } + + override func broadcastPaused() { + // User has requested to pause the broadcast. Samples will stop being delivered. + } + + override func broadcastResumed() { + // User has requested to resume the broadcast. Samples delivery will resume. + } + + override func broadcastFinished() { + // User has requested to finish the broadcast. + DarwinNotificationCenter.shared.postNotification(.broadcastStopped) + clientConnection?.close() + } + + override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { + switch sampleBufferType { + case RPSampleBufferType.video: + // very simple mechanism for adjusting frame rate by using every third frame + frameCount += 1 + if frameCount % 3 == 0 { + uploader?.send(sample: sampleBuffer) + } + default: + break + } + } +} + +private extension SampleHandler { + + func setupConnection() { + clientConnection?.didClose = { [weak self] error in + MXLog.error("client connection did close", context: error) + + if let error = error { + self?.finishBroadcastWithError(error) + } else { + // the displayed failure message is more user friendly when using NSError instead of Error + let JMScreenSharingStopped = 10001 + let customError = NSError(domain: RPRecordingErrorDomain, code: JMScreenSharingStopped, userInfo: [NSLocalizedDescriptionKey: "Screen sharing stopped"]) + self?.finishBroadcastWithError(customError) + } + } + } + + func openConnection() { + let queue = DispatchQueue(label: "broadcast.connectTimer") + let timer = DispatchSource.makeTimerSource(queue: queue) + timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500)) + timer.setEventHandler { [weak self] in + guard self?.clientConnection?.open() == true else { + return + } + + timer.cancel() + } + + timer.resume() + } + + func setupLogger() { + let configuration = MXLogConfiguration() + configuration.logLevel = .verbose + configuration.maxLogFilesCount = 100 + configuration.logFilesSizeLimit = 10 * 1024 * 1024; // 10MB + configuration.subLogName = "broadcastUploadExtension" + + if isatty(STDERR_FILENO) == 0 { + configuration.redirectLogsToFiles = true + } + + MXLog.configure(configuration) + } +} diff --git a/BroadcastUploadExtension/Sources/SampleUploader.swift b/BroadcastUploadExtension/Sources/SampleUploader.swift new file mode 100644 index 000000000..75aa08bed --- /dev/null +++ b/BroadcastUploadExtension/Sources/SampleUploader.swift @@ -0,0 +1,151 @@ +// +// License from the original repository: +// https://github.com/jitsi/jitsi-meet-sdk-samples/blob/master/LICENSE +// +// SampleUploader.swift +// Broadcast Extension +// +// Created by Alex-Dan Bumbu on 22/03/2021. +// Copyright © 2021 8x8, Inc. All rights reserved. +// + +import Foundation +import ReplayKit + +import MatrixSDK + +private enum Constants { + static let bufferMaxLength = 10240 +} + +class SampleUploader { + + private static var imageContext = CIContext(options: nil) + + @Atomic private var isReady = false + private var connection: SocketConnection + + private var dataToSend: Data? + private var byteIndex = 0 + + private let serialQueue: DispatchQueue + + init(connection: SocketConnection) { + self.connection = connection + self.serialQueue = DispatchQueue(label: "org.jitsi.meet.broadcast.sampleUploader") + + setupConnection() + } + + @discardableResult func send(sample buffer: CMSampleBuffer) -> Bool { + guard isReady else { + return false + } + + isReady = false + + dataToSend = prepare(sample: buffer) + byteIndex = 0 + + serialQueue.async { [weak self] in + self?.sendDataChunk() + } + + return true + } +} + +private extension SampleUploader { + + func setupConnection() { + connection.didOpen = { [weak self] in + self?.isReady = true + } + connection.streamHasSpaceAvailable = { [weak self] in + self?.serialQueue.async { + if let success = self?.sendDataChunk() { + self?.isReady = !success + } + } + } + } + + @discardableResult func sendDataChunk() -> Bool { + guard let dataToSend = dataToSend else { + return false + } + + var bytesLeft = dataToSend.count - byteIndex + var length = bytesLeft > Constants.bufferMaxLength ? Constants.bufferMaxLength : bytesLeft + + length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes { + guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else { + return 0 + } + + return connection.writeToStream(buffer: ptr, maxLength: length) + } + + if length > 0 { + byteIndex += length + bytesLeft -= length + + if bytesLeft == 0 { + self.dataToSend = nil + byteIndex = 0 + } + } else { + MXLog.error("writeBufferToStream failure") + } + + return true + } + + func prepare(sample buffer: CMSampleBuffer) -> Data? { + guard let imageBuffer = CMSampleBufferGetImageBuffer(buffer) else { + MXLog.error("image buffer not available") + return nil + } + + CVPixelBufferLockBaseAddress(imageBuffer, .readOnly) + + let scaleFactor = 2.0 + let width = CVPixelBufferGetWidth(imageBuffer)/Int(scaleFactor) + let height = CVPixelBufferGetHeight(imageBuffer)/Int(scaleFactor) + let orientation = CMGetAttachment(buffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uintValue ?? 0 + + let scaleTransform = CGAffineTransform(scaleX: CGFloat(1.0/scaleFactor), y: CGFloat(1.0/scaleFactor)) + let bufferData = self.jpegData(from: imageBuffer, scale: scaleTransform) + + CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly) + + guard let messageData = bufferData else { + MXLog.error("corrupted image buffer") + return nil + } + + let httpResponse = CFHTTPMessageCreateResponse(nil, 200, nil, kCFHTTPVersion1_1).takeRetainedValue() + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Content-Length" as CFString, String(messageData.count) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Width" as CFString, String(width) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Height" as CFString, String(height) as CFString) + CFHTTPMessageSetHeaderFieldValue(httpResponse, "Buffer-Orientation" as CFString, String(orientation) as CFString) + + CFHTTPMessageSetBody(httpResponse, messageData as CFData) + + let serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse)?.takeRetainedValue() as Data? + + return serializedMessage + } + + func jpegData(from buffer: CVPixelBuffer, scale scaleTransform: CGAffineTransform) -> Data? { + let image = CIImage(cvPixelBuffer: buffer).transformed(by: scaleTransform) + + guard let colorSpace = image.colorSpace else { + return nil + } + + let options: [CIImageRepresentationOption: Float] = [kCGImageDestinationLossyCompressionQuality as CIImageRepresentationOption: 1.0] + + return SampleUploader.imageContext.jpegRepresentation(of: image, colorSpace: colorSpace, options: options) + } +} diff --git a/BroadcastUploadExtension/Sources/SocketConnection.swift b/BroadcastUploadExtension/Sources/SocketConnection.swift new file mode 100644 index 000000000..7b1285fd9 --- /dev/null +++ b/BroadcastUploadExtension/Sources/SocketConnection.swift @@ -0,0 +1,203 @@ +// +// License from the original repository: +// https://github.com/jitsi/jitsi-meet-sdk-samples/blob/master/LICENSE +// +// SocketConnection.swift +// Broadcast Extension +// +// Created by Alex-Dan Bumbu on 22/03/2021. +// Copyright © 2021 Atlassian Inc. All rights reserved. +// + +import Foundation + +import MatrixSDK + +class SocketConnection: NSObject { + var didOpen: (() -> Void)? + var didClose: ((Error?) -> Void)? + var streamHasSpaceAvailable: (() -> Void)? + + private let filePath: String + private var socketHandle: Int32 = -1 + private var address: sockaddr_un? + + private var inputStream: InputStream? + private var outputStream: OutputStream? + + private var networkQueue: DispatchQueue? + private var shouldKeepRunning = false + + init?(filePath path: String) { + filePath = path + socketHandle = Darwin.socket(AF_UNIX, SOCK_STREAM, 0) + + guard socketHandle != -1 else { + MXLog.error("failure: create socket") + return nil + } + } + + func open() -> Bool { + MXLog.info("open socket connection") + + guard FileManager.default.fileExists(atPath: filePath) else { + MXLog.error("failure: socket file missing") + return false + } + + guard setupAddress() == true else { + return false + } + + guard connectSocket() == true else { + return false + } + + setupStreams() + + inputStream?.open() + outputStream?.open() + + return true + } + + func close() { + unscheduleStreams() + + inputStream?.delegate = nil + outputStream?.delegate = nil + + inputStream?.close() + outputStream?.close() + + inputStream = nil + outputStream = nil + } + + func writeToStream(buffer: UnsafePointer, maxLength length: Int) -> Int { + outputStream?.write(buffer, maxLength: length) ?? 0 + } +} + +extension SocketConnection: StreamDelegate { + + func stream(_ aStream: Stream, handle eventCode: Stream.Event) { + switch eventCode { + case .openCompleted: + MXLog.info("client stream open completed") + if aStream == outputStream { + didOpen?() + } + case .hasBytesAvailable: + if aStream == inputStream { + var buffer: UInt8 = 0 + let numberOfBytesRead = inputStream?.read(&buffer, maxLength: 1) + if numberOfBytesRead == 0 && aStream.streamStatus == .atEnd { + MXLog.info("server socket closed") + close() + notifyDidClose(error: nil) + } + } + case .hasSpaceAvailable: + if aStream == outputStream { + streamHasSpaceAvailable?() + } + case .errorOccurred: + MXLog.error("client stream error occured", context: aStream.streamError) + close() + notifyDidClose(error: aStream.streamError) + + default: + break + } + } +} + +private extension SocketConnection { + + func setupAddress() -> Bool { + var addr = sockaddr_un() + guard filePath.count < MemoryLayout.size(ofValue: addr.sun_path) else { + MXLog.error("failure: fd path is too long") + return false + } + + _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in + filePath.withCString { + strncpy(ptr, $0, filePath.count) + } + } + + address = addr + return true + } + + func connectSocket() -> Bool { + guard var addr = address else { + return false + } + + let status = withUnsafePointer(to: &addr) { ptr in + ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { + Darwin.connect(socketHandle, $0, socklen_t(MemoryLayout.size)) + } + } + + guard status == noErr else { + MXLog.error("connect socket failure", context: status) + return false + } + + return true + } + + func setupStreams() { + var readStream: Unmanaged? + var writeStream: Unmanaged? + + CFStreamCreatePairWithSocket(kCFAllocatorDefault, socketHandle, &readStream, &writeStream) + + inputStream = readStream?.takeRetainedValue() + inputStream?.delegate = self + inputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String)) + + outputStream = writeStream?.takeRetainedValue() + outputStream?.delegate = self + outputStream?.setProperty(kCFBooleanTrue, forKey: Stream.PropertyKey(kCFStreamPropertyShouldCloseNativeSocket as String)) + + scheduleStreams() + } + + func scheduleStreams() { + shouldKeepRunning = true + + networkQueue = DispatchQueue.global(qos: .userInitiated) + networkQueue?.async { [weak self] in + self?.inputStream?.schedule(in: .current, forMode: .common) + self?.outputStream?.schedule(in: .current, forMode: .common) + RunLoop.current.run() + + var isRunning = false + + repeat { + isRunning = self?.shouldKeepRunning ?? false && RunLoop.current.run(mode: .default, before: .distantFuture) + } while (isRunning) + } + } + + func unscheduleStreams() { + networkQueue?.sync { [weak self] in + self?.inputStream?.remove(from: .current, forMode: .common) + self?.outputStream?.remove(from: .current, forMode: .common) + } + + shouldKeepRunning = false + } + + func notifyDidClose(error: Error?) { + if didClose != nil { + didClose?(error) + } + } +} diff --git a/BroadcastUploadExtension/SupportingFiles/BroadcastUploadExtension.entitlements b/BroadcastUploadExtension/SupportingFiles/BroadcastUploadExtension.entitlements new file mode 100644 index 000000000..f1123e644 --- /dev/null +++ b/BroadcastUploadExtension/SupportingFiles/BroadcastUploadExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + $(APPLICATION_GROUP_IDENTIFIER) + + + diff --git a/BroadcastUploadExtension/SupportingFiles/Info.plist b/BroadcastUploadExtension/SupportingFiles/Info.plist new file mode 100644 index 000000000..34e3187b8 --- /dev/null +++ b/BroadcastUploadExtension/SupportingFiles/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundlePackageType + XPC! + CFBundleName + $(PRODUCT_NAME) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleDisplayName + $(BUNDLE_DISPLAY_NAME) + CFBundleDevelopmentRegion + en + NSExtension + + NSExtensionPointIdentifier + com.apple.broadcast-services-upload + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SampleHandler + RPBroadcastProcessMode + RPBroadcastProcessModeSampleBuffer + + + diff --git a/BroadcastUploadExtension/target.yml b/BroadcastUploadExtension/target.yml new file mode 100644 index 000000000..1ef21b6b3 --- /dev/null +++ b/BroadcastUploadExtension/target.yml @@ -0,0 +1,42 @@ +name: BroadcastUploadExtension + +schemes: + BroadcastUploadExtension: + analyze: + config: Debug + archive: + config: Release + build: + targets: + BroadcastUploadExtension: + - running + - testing + - profiling + - analyzing + - archiving + profile: + config: Release + run: + askForAppToLaunch: true + config: Debug + debugEnabled: false + disableMainThreadChecker: true + launchAutomaticallySubstyle: 2 + test: + config: Debug + disableMainThreadChecker: true + +targets: + BroadcastUploadExtension: + platform: iOS + type: app-extension + + configFiles: + Debug: Debug.xcconfig + Release: Release.xcconfig + + sources: + - path: . + - path: ../Config/BuildSettings.swift + - path: ../Riot/Categories/Bundle.swift + - path: ../Riot/Modules/Room/TimelineCells/Styles/RoomTimelineStyleIdentifier.swift diff --git a/Config/AppIdentifiers.xcconfig b/Config/AppIdentifiers.xcconfig index a55dc348f..3dedb84e3 100644 --- a/Config/AppIdentifiers.xcconfig +++ b/Config/AppIdentifiers.xcconfig @@ -37,3 +37,6 @@ SHARE_EXTENSION_PROVISIONING_PROFILE = 8c797ca0-0440-49bd-be8d-11d761152995 SIRI_INTENTS_PROVISIONING_PROFILE_SPECIFIER = "Vector Siri Intents: App Store" SIRI_INTENTS_PROVISIONING_PROFILE = 1690e81a-5ad3-4d99-b578-02693579be71 + +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER = "Vector Broadcast Upload Extension: App Store" +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE = c86239f4-0d3a-47f4-a5f2-9f4763c42b5d diff --git a/Config/Project.xcconfig b/Config/Project.xcconfig index 95b6295d6..bbca50a1b 100644 --- a/Config/Project.xcconfig +++ b/Config/Project.xcconfig @@ -23,6 +23,7 @@ // Application constants KEYCHAIN_ACCESS_GROUP = $(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER).keychain.shared +BROADCAST_UPLOAD_EXTENSION_BUNDLE_IDENTIFIER = $(BASE_BUNDLE_IDENTIFIER).broadcastUploadExtension // Build settings IPHONEOS_DEPLOYMENT_TARGET = 14.0 diff --git a/Podfile b/Podfile index 52ce26306..f2230ef61 100644 --- a/Podfile +++ b/Podfile @@ -127,6 +127,10 @@ abstract_target 'RiotPods' do import_MatrixKit_pods end + target "BroadcastUploadExtension" do + import_MatrixSDK + end + end post_install do |installer| diff --git a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.m b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.m index 39552e4f0..71d528331 100644 --- a/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.m +++ b/Riot/Modules/Integrations/Widgets/Jitsi/JitsiViewController.m @@ -35,6 +35,7 @@ static NSString * _Nonnull kRCTTextViewClassName = @"RCTTextView"; Some feature flags defined in https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/flags/constants.js */ static NSString * _Nonnull kJitsiFeatureFlagChatEnabled = @"chat.enabled"; +static NSString * _Nonnull kJitsiFeatureFlagScreenSharingEnabled = @"ios.screensharing.enabled"; @interface JitsiViewController () @@ -278,6 +279,7 @@ static NSString * _Nonnull kJitsiFeatureFlagChatEnabled = @"chat.enabled"; andAvatar:avatarUrl]; builder.token = self.jwtToken; [builder setFeatureFlag:kJitsiFeatureFlagChatEnabled withBoolean:NO]; + [builder setFeatureFlag:kJitsiFeatureFlagScreenSharingEnabled withBoolean: YES]; }]; [self.jitsiMeetView join:jitsiMeetConferenceOptions]; diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist index 5271d2330..e1ed4aff1 100644 --- a/Riot/SupportingFiles/Info.plist +++ b/Riot/SupportingFiles/Info.plist @@ -4,6 +4,10 @@ CFBundleDevelopmentRegion en + RTCAppGroupIdentifier + $(APPLICATION_GROUP_IDENTIFIER) + RTCScreenSharingExtension + $(BROADCAST_UPLOAD_EXTENSION_BUNDLE_IDENTIFIER) CFBundleDisplayName $(BUNDLE_DISPLAY_NAME) CFBundleDocumentTypes diff --git a/Riot/target.yml b/Riot/target.yml index 8cd537cd3..8139be715 100644 --- a/Riot/target.yml +++ b/Riot/target.yml @@ -37,6 +37,7 @@ targets: - target: RiotShareExtension - target: SiriIntents - target: RiotNSE + - target: BroadcastUploadExtension - target: DesignKit - target: CommonKit - package: AnalyticsEvents diff --git a/Variants/Alpha/Config/AppIdentifiers.xcconfig b/Variants/Alpha/Config/AppIdentifiers.xcconfig index 6afbea548..1b4194ce1 100644 --- a/Variants/Alpha/Config/AppIdentifiers.xcconfig +++ b/Variants/Alpha/Config/AppIdentifiers.xcconfig @@ -38,3 +38,6 @@ SHARE_EXTENSION_PROVISIONING_PROFILE = b47f96e0-647b-4274-b2bb-8103b9a97146 SIRI_INTENTS_PROVISIONING_PROFILE_SPECIFIER = "Alpha Vector Siri Intents Ad hoc" SIRI_INTENTS_PROVISIONING_PROFILE = a9fdb684-a68c-4207-afc2-810f3287b1f5 + +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER = "Alpha Vector Broadcast Upload Extension Ad hoc" +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE = 3616761e-f7f6-4f38-bf74-13bc6b0a1000 diff --git a/Variants/Alpha/fastlane/.env.default b/Variants/Alpha/fastlane/.env.default index 94364535d..53910bfef 100644 --- a/Variants/Alpha/fastlane/.env.default +++ b/Variants/Alpha/fastlane/.env.default @@ -7,11 +7,13 @@ MAIN_TARGET=Riot SHARE_EXTENSION_TARGET=RiotShareExtension SIRI_INTENTS_EXTENSION_TARGET=SiriIntents NSE_TARGET=RiotNSE +BROADCAST_UPLOAD_EXTENSION_TARGET=BroadcastUploadExtension MAIN_BUNDLE_ID=im.vector.app.alpha SHARE_EXTENSION_BUNDLE_ID=im.vector.app.alpha.shareExtension SIRI_INTENTS_EXTENSION_BUNDLE_ID=im.vector.app.alpha.SiriIntents NSE_BUNDLE_ID=im.vector.app.alpha.nse +BROADCAST_UPLOAD_EXTENSION_BUNDLE_ID=im.vector.app.alpha.broadcastUploadExtension ## Build configuration @@ -29,6 +31,7 @@ MAIN_PROVISIONING_PROFILE_FILENAME=main.mobileprovision SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME=share_extension.mobileprovision SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_FILENAME=siri_intents.mobileprovision NSE_PROVISIONING_PROFILE_FILENAME=nse.mobileprovision +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_FILENAME=broadcast_upload_extension.mobileprovision ## App Store code signing @@ -39,6 +42,7 @@ APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER="Vector App Store" APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Share Extension: App Store" APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Siri Intents: App Store" APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER="Vector NSE: App Store" +APPSTORE_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Broadcast Upload Extension: App Store" ## Ad-Hoc code signing @@ -46,6 +50,7 @@ ADHOC_MAIN_PROVISIONING_PROFILE_SPECIFIER="Alpha Vector App Ad hoc" ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Alpha Vector Share Extension Ad hoc" ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Alpha Vector Siri Intents Ad hoc" ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER="Alpha Vector NSE Ad hoc" +ADHOC_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Alpha Vector Broadcast Upload Extension Ad hoc" ## Account information diff --git a/changelog.d/7566.feature b/changelog.d/7566.feature new file mode 100644 index 000000000..11681dd13 --- /dev/null +++ b/changelog.d/7566.feature @@ -0,0 +1 @@ +Broadcast Upload Extension added to the app targets to allow Jitsi screen sharing feature. \ No newline at end of file diff --git a/fastlane/.env.default b/fastlane/.env.default index fed77cc74..679f415b3 100644 --- a/fastlane/.env.default +++ b/fastlane/.env.default @@ -7,11 +7,13 @@ MAIN_TARGET=Riot SHARE_EXTENSION_TARGET=RiotShareExtension SIRI_INTENTS_EXTENSION_TARGET=SiriIntents NSE_TARGET=RiotNSE +BROADCAST_UPLOAD_EXTENSION_TARGET=BroadcastUploadExtension MAIN_BUNDLE_ID=im.vector.app SHARE_EXTENSION_BUNDLE_ID=im.vector.app.shareExtension SIRI_INTENTS_EXTENSION_BUNDLE_ID=im.vector.app.SiriIntents NSE_BUNDLE_ID=im.vector.app.nse +BROADCAST_UPLOAD_EXTENSION_BUNDLE_ID=im.vector.app.broadcastUploadExtension ## Build configuration @@ -29,6 +31,7 @@ MAIN_PROVISIONING_PROFILE_FILENAME=main.mobileprovision SHARE_EXTENSION_PROVISIONING_PROFILE_FILENAME=share_extension.mobileprovision SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_FILENAME=siri_intents.mobileprovision NSE_PROVISIONING_PROFILE_FILENAME=nse.mobileprovision +BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_FILENAME=broadcast_upload_extension.mobileprovision ## App Store code signing @@ -39,6 +42,7 @@ APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER="Vector App Store" APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Share Extension: App Store" APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Siri Intents: App Store" APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER="Vector NSE: App Store" +APPSTORE_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Broadcast Upload Extension: App Store" ## Ad-Hoc code signing @@ -46,6 +50,7 @@ ADHOC_MAIN_PROVISIONING_PROFILE_SPECIFIER="Vector Ad Hoc" ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Share Extension: Ad Hoc" ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Siri Intents: Ad Hoc" ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER="Vector NSE: Ad Hoc" +ADHOC_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER="Vector Broadcast Upload Extension: Ad Hoc" ## Account information diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c451f1533..90d3ff0d7 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -290,12 +290,14 @@ platform :ios do share_extension_provisioning_profile = ENV["ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_profile = ENV["ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] nse_provisioning_profile = ENV["ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER"] + broadcast_upload_extension_provisioning_profile = ENV["ADHOC_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] else export_method = "app-store" main_provisioning_profile = ENV["APPSTORE_MAIN_PROVISIONING_PROFILE_SPECIFIER"] share_extension_provisioning_profile = ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_profile = ENV["APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] nse_provisioning_profile = ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"] + broadcast_upload_extension_provisioning_profile = ENV["APPSTORE_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] end # Build app and create ipa @@ -321,6 +323,7 @@ platform :ios do ENV["SHARE_EXTENSION_BUNDLE_ID"] => share_extension_provisioning_profile, ENV["NSE_BUNDLE_ID"] => nse_provisioning_profile, ENV["SIRI_INTENTS_EXTENSION_BUNDLE_ID"] => siri_intents_provisioning_profile, + ENV["BROADCAST_UPLOAD_EXTENSION_BUNDLE_ID"] => broadcast_upload_extension_provisioning_profile, }, iCloudContainerEnvironment: "Production", }, @@ -340,6 +343,7 @@ platform :ios do share_extension_provisioning_name = adhoc ? ENV["ADHOC_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_SHARE_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] siri_intents_provisioning_name = adhoc ? ENV["ADHOC_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_SIRI_INTENTS_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] notification_service_extension_provisioning_name = adhoc ? ENV["ADHOC_NSE_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_NSE_PROVISIONING_PROFILE_SPECIFIER"] + broadcast_upload_extension_provisioning_name = adhoc ? ENV["ADHOC_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] : ENV["APPSTORE_BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_SPECIFIER"] # Main application get_provisioning_profile( @@ -385,6 +389,17 @@ platform :ios do filename: ENV["NSE_PROVISIONING_PROFILE_FILENAME"], readonly: true, ) + # Broadcast Upload Extension + get_provisioning_profile( + app_identifier: ENV["BROADCAST_UPLOAD_EXTENSION_BUNDLE_ID"], + provisioning_name: broadcast_upload_extension_provisioning_name, + ignore_profiles_with_different_name: true, + adhoc: adhoc, + skip_certificate_verification: skip_certificate_verification, + output_path: output_path, + filename: ENV["BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_FILENAME"], + readonly: true, + ) end desc "Update provisioning profiles for each target" @@ -421,6 +436,13 @@ platform :ios do target_filter: ENV["NSE_TARGET"], build_configuration: build_configuration, ) + # Broadcast Upload Extension + update_project_provisioning( + xcodeproj: xcodeproj, + profile: "#{provisioning_profiles_path}#{ENV["BROADCAST_UPLOAD_EXTENSION_PROVISIONING_PROFILE_FILENAME"]}", + target_filter: ENV["BROADCAST_UPLOAD_EXTENSION_TARGET"], + build_configuration: build_configuration, + ) end desc "Update application build number for all targets" diff --git a/project.yml b/project.yml index 3922de651..449ee955c 100644 --- a/project.yml +++ b/project.yml @@ -32,6 +32,7 @@ include: - path: RiotShareExtension/target.yml - path: SiriIntents/target.yml - path: RiotNSE/target.yml + - path: BroadcastUploadExtension/target.yml - path: DesignKit/target.yml - path: RiotSwiftUI/target.yml - path: RiotSwiftUI/targetUnitTests.yml