Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement getDisplayMedia #424

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d3abec4
implement basic getDisplayMedia support
hthetiot Oct 28, 2019
9b8f134
remove missing debug js/getUserMedia-m69.js js/getUserMedia.js.new
hthetiot Oct 28, 2019
c83dd81
Merge branch 'master' into task/getDisplayMedia
hthetiot Oct 28, 2019
504bfab
add PluginGetDisplayMedia.swift to plugin.xml
hthetiot Oct 28, 2019
dfe28db
update dist/cordova-plugin-iosrtc.js build
hthetiot Oct 28, 2019
d19c20c
implement PluginGetDisplayMedia initial working prototype
hthetiot Oct 28, 2019
34c29a6
fix getDisplayMedia iosrtcPlugin.swift ident
hthetiot Oct 28, 2019
7b2da7a
implement PluginRTCScreenCaptureController with PluginRTCVideoCapture…
hthetiot Oct 28, 2019
bf6916c
fix tarvis lint
hthetiot Oct 29, 2019
b32cf3b
Merge branch 'master' into task/getDisplayMedia
hthetiot Oct 29, 2019
44302c0
merge master
hthetiot Oct 30, 2019
813a7b9
fix travis build
hthetiot Oct 30, 2019
91371ac
merge master
hthetiot Nov 21, 2019
b79a8b3
Merge branch 'master' into task/getDisplayMedia
hthetiot Jan 17, 2020
0d005c5
Merge branch 'master' into task/getDisplayMedia
hthetiot Feb 25, 2020
4d78f6c
merge master
hthetiot Apr 29, 2020
e9c4679
merge upstream
hthetiot Apr 29, 2020
5557519
Update PluginGetUserMedia.swift
hthetiot May 21, 2020
f219a1e
merge master
hthetiot May 28, 2020
d0af8df
Merge branch 'task/getDisplayMedia' of github.com:cordova-rtc/cordova…
hthetiot May 28, 2020
677ee72
merge master
hthetiot Jul 17, 2020
e134d4e
merge master
hthetiot Sep 1, 2020
38dfcd9
merge master
hthetiot Sep 1, 2020
d1b95a7
Merge branch 'master' into task/getDisplayMedia
hthetiot Oct 18, 2020
7697788
merge master
hthetiot Jul 2, 2021
9b840a5
fix lint
hthetiot Jul 2, 2021
f6a0615
fix error: value of type 'PluginRTCVideoCaptureController' has no mem…
hthetiot Jul 5, 2021
de73fba
bugfix: check if completionHandler is nil
be-ndee Sep 10, 2021
97a1a26
bugfix: use stopCapture instead of stopRecording
be-ndee Sep 10, 2021
bf33a30
Merge pull request #708 from 4morgen-gmbh/task/getDisplayMedia
hthetiot Sep 10, 2021
eb34a21
Merge branch 'master' into task/getDisplayMedia
hthetiot May 12, 2022
d02b7e7
Merge branch 'task/getDisplayMedia' of github.com:cordova-rtc/cordova…
hthetiot May 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions js/getDisplayMedia.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Expose the getDisplayMedia function.
*/
module.exports = getDisplayMedia;

/**
* Dependencies.
*/
var debug = require('debug')('iosrtc:getDisplayMedia'),
debugerror = require('debug')('iosrtc:ERROR:getDisplayMedia'),
exec = require('cordova/exec'),
MediaStream = require('./MediaStream'),
Errors = require('./Errors');

function getDisplayMedia(constraints) {
// Detect callback usage to assist 5.0.1 to 5.0.2 migration
// TODO remove on 6.0.0
Errors.detectDeprecatedCallbaksUsage('cordova.plugins.iosrtc.getDisplayMedia', arguments);

debug('[original constraints:%o]', constraints);

var newConstraints = {};

if (
typeof constraints !== 'object' ||
(!constraints.hasOwnProperty('audio') && !constraints.hasOwnProperty('video'))
) {
return new Promise(function (resolve, reject) {
reject(
new Errors.MediaStreamError(
'constraints must be an object with at least "audio" or "video" keys'
)
);
});
}

debug('[computed constraints:%o]', newConstraints);

return new Promise(function (resolve, reject) {
function onResultOK(data) {
debug('getDisplayMedia() | success');
var stream = MediaStream.create(data.stream);
resolve(stream);
// Emit "connected" on the stream.
stream.emitConnected();
}

function onResultError(error) {
debugerror('getDisplayMedia() | failure: %s', error);
reject(new Errors.MediaStreamError('getDisplayMedia() failed: ' + error));
}

exec(onResultOK, onResultError, 'iosrtcPlugin', 'getDisplayMedia', [newConstraints]);
});
}
4 changes: 4 additions & 0 deletions js/iosrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var // Dictionary of MediaStreamRenderers.
exec = require('cordova/exec'),
domready = require('domready'),
getUserMedia = require('./getUserMedia'),
getDisplayMedia = require('./getDisplayMedia'),
enumerateDevices = require('./enumerateDevices'),
RTCPeerConnection = require('./RTCPeerConnection'),
RTCSessionDescription = require('./RTCSessionDescription'),
Expand All @@ -33,6 +34,7 @@ var // Dictionary of MediaStreamRenderers.
module.exports = {
// Expose WebRTC classes and functions.
getUserMedia: getUserMedia,
getDisplayMedia: getDisplayMedia,
enumerateDevices: enumerateDevices,
getMediaDevices: enumerateDevices, // TMP
RTCPeerConnection: RTCPeerConnection,
Expand Down Expand Up @@ -190,6 +192,8 @@ function registerGlobals(doNotRestoreCallbacksSupport) {
restoreCallbacksSupport();
}

navigator.getDisplayMedia = getDisplayMedia;
navigator.mediaDevices.getDisplayMedia = getDisplayMedia;
navigator.getUserMedia = getUserMedia;
navigator.webkitGetUserMedia = getUserMedia;

Expand Down
1 change: 1 addition & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<source-file src="src/PluginMediaStream.swift" />
<source-file src="src/PluginMediaStreamTrack.swift" />
<source-file src="src/PluginGetUserMedia.swift" />
<source-file src="src/PluginGetDisplayMedia.swift" />
<source-file src="src/PluginEnumerateDevices.swift" />
<source-file src="src/PluginUtils.swift" />
<source-file src="src/PluginMediaStreamRenderer.swift" />
Expand Down
94 changes: 94 additions & 0 deletions src/PluginGetDisplayMedia.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import Foundation
import AVFoundation
import ReplayKit

class PluginGetDisplayMedia {

var rtcPeerConnectionFactory: RTCPeerConnectionFactory

init(rtcPeerConnectionFactory: RTCPeerConnectionFactory) {
NSLog("PluginGetDisplayMedia#init()")
self.rtcPeerConnectionFactory = rtcPeerConnectionFactory
}

deinit {
NSLog("PluginGetDisplayMedia#deinit()")
}

func call(
_ constraints: NSDictionary,
callback: @escaping (_ data: NSDictionary) -> Void,
errback: @escaping (_ error: String) -> Void,
eventListenerForNewStream: @escaping (_ pluginMediaStream: PluginMediaStream) -> Void
) {

NSLog("PluginGetDisplayMedia#call()")
if #available(iOS 11.0, *) {

let recorder = RPScreenRecorder.shared()

if (recorder.isRecording) {
recorder.stopCapture(handler: {(error) in
if (error != nil) {
errback(error!.localizedDescription)
} else {
self.startCapture(recorder: recorder, callback: callback, errback: errback, eventListenerForNewStream: eventListenerForNewStream)
}
})
} else if (recorder.isAvailable) {
self.startCapture(recorder: recorder, callback: callback, errback: errback, eventListenerForNewStream: eventListenerForNewStream)
} else {
errback("Screen recorder is not available!")
}

} else {
errback("Screen recorder is not available!")
}
}

@available(iOS 11.0, *)
func startCapture(
recorder: RPScreenRecorder,
callback: @escaping (_ data: NSDictionary) -> Void,
errback: @escaping (_ error: String) -> Void,
eventListenerForNewStream: @escaping (_ pluginMediaStream: PluginMediaStream) -> Void
) {

let rtcVideoSource: RTCVideoSource = self.rtcPeerConnectionFactory.videoSource()
let videoCapturer: RTCVideoCapturer = RTCVideoCapturer(delegate: rtcVideoSource)
let rtcMediaStream: RTCMediaStream = self.rtcPeerConnectionFactory.mediaStream(withStreamId: UUID().uuidString)
let rtcVideoTrack: RTCVideoTrack = self.rtcPeerConnectionFactory.videoTrack(
with: rtcVideoSource, trackId: UUID().uuidString)

let videoCaptureController: PluginRTCScreenCaptureController = PluginRTCScreenCaptureController(capturer: videoCapturer, recorder: recorder, source: rtcVideoSource)
rtcVideoTrack.videoCaptureController = videoCaptureController

// TODO use startCapture completionHandler
let captureStarted = videoCaptureController.startCapture()
if (!captureStarted) {
errback("constraints failed")
return
}

// If videoSource state is "ended" it means that constraints were not satisfied so
// invoke the given errback.
if (rtcVideoSource.state == RTCSourceState.ended) {
NSLog("PluginGetDisplayMedia() | rtcVideoSource.state is 'ended', constraints not satisfied")

errback("constraints not satisfied")
return
}

rtcMediaStream.addVideoTrack(rtcVideoTrack)

let pluginMediaStream: PluginMediaStream = PluginMediaStream(rtcMediaStream: rtcMediaStream)
pluginMediaStream.run()

// Let the plugin store it in its dictionary.
eventListenerForNewStream(pluginMediaStream)

callback([
"stream": pluginMediaStream.getJSON()
])
}
}
2 changes: 1 addition & 1 deletion src/PluginGetUserMedia.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class PluginGetUserMedia {
// Ignore Simulator cause does not support Camera
#if !targetEnvironment(simulator)
let videoCapturer: RTCCameraVideoCapturer = RTCCameraVideoCapturer(delegate: rtcVideoSource!)
let videoCaptureController: PluginRTCVideoCaptureController = PluginRTCVideoCaptureController(capturer: videoCapturer)
let videoCaptureController: PluginRTCCameraCaptureController = PluginRTCCameraCaptureController(capturer: videoCapturer)
rtcVideoTrack!.videoCaptureController = videoCaptureController

let constraintsSatisfied = videoCaptureController.setConstraints(constraints: videoConstraints)
Expand Down
2 changes: 2 additions & 0 deletions src/PluginMediaStreamTrack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@ class PluginMediaStreamTrack : NSObject {
}
}

/*
func switchCamera() {
self.rtcMediaStreamTrack.videoCaptureController?.switchCamera()
}
*/

func registerRender(render: PluginMediaStreamRenderer) {
if let exist = self.renders[render.id] {
Expand Down
128 changes: 116 additions & 12 deletions src/PluginRTCVideoCaptureController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import AVFoundation
import ReplayKit

import func ObjectiveC.objc_getAssociatedObject
import func ObjectiveC.objc_setAssociatedObject
Expand Down Expand Up @@ -52,22 +53,123 @@ extension RTCMediaStreamTrack {

class PluginRTCVideoCaptureController : NSObject {

var isCapturing: Bool = false;

// Default to the front camera.
var device: AVCaptureDevice?
var deviceFormat: AVCaptureDevice.Format?
var deviceFrameRate: Int?

var constraints: NSDictionary = [:]

func startCapture(completionHandler: ((Error?) -> Void)? = nil) -> Bool {
return isCapturing;
}

func stopCapture() {

}
}

class PluginRTCScreenCaptureController : PluginRTCVideoCaptureController {

var capturer: RTCVideoCapturer
var source: RTCVideoSource
var recorder: RPScreenRecorder

init(capturer: RTCVideoCapturer, recorder: RPScreenRecorder, source: RTCVideoSource) {
self.capturer = capturer
self.recorder = recorder;
self.source = source;
}

override func startCapture(completionHandler: ((Error?) -> Void)? = nil) -> Bool {

// Stop previous capture in case of setConstraints, followed by startCapture
// aka future applyConstraints
if (isCapturing) {
stopCapture();
}

if #available(iOS 11.0, *) {
recorder.isMicrophoneEnabled = false
recorder.startCapture(handler: {(sampleBuffer, bufferType, error) in
if (bufferType == RPSampleBufferType.video) {
self.handleSourceBuffer(
source: self.source,
sampleBuffer: sampleBuffer,
sampleType: bufferType
)
}
if (completionHandler != nil && error != nil) {
completionHandler!(error)
}
}) { (error) in
if (completionHandler != nil && error != nil) {
completionHandler!(error)
} else {
// TODO Optional closure parameter
//completionHandler!()
}
}

isCapturing = true

NSLog("PluginRTCScreenCaptureController#startCapture Capture started");
}

return isCapturing;
}

override func stopCapture() {
// TODO: stopCaptureWithCompletionHandler with DispatchSemaphore
if (isCapturing) {
if #available(iOS 11.0, *) {
recorder.stopCapture {(error) in
// TODO debug error
}
isCapturing = false

NSLog("PluginRTCScreenCaptureController#stopCapture Capture stopped");
}
}
}

func handleSourceBuffer(source: RTCVideoSource, sampleBuffer: CMSampleBuffer, sampleType: RPSampleBufferType) {
if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) ||
!CMSampleBufferDataIsReady(sampleBuffer)) {
return;
}

let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
if (pixelBuffer == nil) {
return;
}

let width = CVPixelBufferGetWidth(pixelBuffer!);
let height = CVPixelBufferGetHeight(pixelBuffer!);

source.adaptOutputFormat(toWidth: Int32(width/2), height: Int32(height/2), fps: 8)

let rtcPixelBuffer = RTCCVPixelBuffer(pixelBuffer: pixelBuffer!)
let timeStampNs =
CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * Float64(NSEC_PER_SEC)
let videoFrame = RTCVideoFrame(buffer: rtcPixelBuffer, rotation: RTCVideoRotation._0, timeStampNs: Int64(timeStampNs))
source.capturer(capturer, didCapture: videoFrame)
}
}

class PluginRTCCameraCaptureController : PluginRTCVideoCaptureController {

private let DEFAULT_HEIGHT : Int32 = 480
private let DEFAULT_WIDTH : Int32 = 640

private let DEFAULT_FPS : Int = 15
private let DEFAULT_ASPECT_RATIO : Float32 = 4/3
private let FACING_MODE_USER : String = "user";
private let FACING_MODE_ENV : String = "environment";

var capturer: RTCCameraVideoCapturer
var isCapturing: Bool = false;

// Default to the front camera.
var device: AVCaptureDevice?
var deviceFormat: AVCaptureDevice.Format?
var deviceFrameRate: Int?

var constraints: NSDictionary = [:]

init(capturer: RTCCameraVideoCapturer) {
self.capturer = capturer
Expand Down Expand Up @@ -139,7 +241,7 @@ class PluginRTCVideoCaptureController : NSObject {
return device != nil;
}

func startCapture() -> Bool {
override func startCapture(completionHandler: ((Error?) -> Void)? = nil) -> Bool {

// Stop previous capture in case of setConstraints, followed by startCapture
// aka future applyConstraints
Expand Down Expand Up @@ -167,10 +269,10 @@ class PluginRTCVideoCaptureController : NSObject {

NSLog("PluginRTCVideoCaptureController#startCapture Capture started, device:%@, format:%@", device!, deviceFormat!);

return true;
return isCapturing;
}

func stopCapture() {
override func stopCapture() {
// TODO: stopCaptureWithCompletionHandler with DispatchSemaphore
if (isCapturing) {
capturer.stopCapture()
Expand Down Expand Up @@ -294,6 +396,7 @@ class PluginRTCVideoCaptureController : NSObject {
return captureDevices.firstObject as? AVCaptureDevice
}

/*
func switchCamera() -> Bool {

if (self.capturer.captureSession.isRunning) {
Expand All @@ -306,6 +409,7 @@ class PluginRTCVideoCaptureController : NSObject {

return self.startCapture()
}
*/

fileprivate func findAlternativeDevicePosition(currentDevice: AVCaptureDevice?) -> AVCaptureDevice? {
let captureDevices: NSArray = RTCCameraVideoCapturer.captureDevices() as NSArray
Expand Down Expand Up @@ -468,7 +572,7 @@ class PluginRTCVideoCaptureController : NSObject {

return selectedFormat
}

//
// constraints parsers
//
Expand Down
Loading