diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..deecbcd --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 8e2a021..95fbc33 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index fbce1ec..4704a41 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,12 +1,12 @@ plugins { id 'com.android.application' + id 'org.jetbrains.kotlin.android' } // Load values from SA properties file. def propsFile = rootProject.file('sa.properties') def props = new Properties() props.load(new FileInputStream(propsFile)) - repositories { mavenLocal() // Millicast SDK via Maven from GitHub Packages @@ -29,15 +29,15 @@ android { keyPassword props['keyPassword'] } } - compileSdkVersion 31 + compileSdkVersion 34 defaultConfig { applicationId "com.millicast.android_java_sdk_sa" - minSdkVersion 24 + minSdkVersion 33 //noinspection ExpiredTargetSdkVersion - targetSdkVersion 28 + targetSdkVersion 34 versionCode 11 - versionName "1.5.2" + versionName "1.7.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -50,9 +50,10 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } + namespace 'com.millicast.android_app' } dependencies { @@ -66,13 +67,15 @@ dependencies { implementation 'androidx.navigation:navigation-ui:2.4.1' implementation 'androidx.drawerlayout:drawerlayout:1.1.1' implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.core:core-ktx:1.10.0' + implementation 'io.dolby:promise:2.9.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' // Add Millicast SDK via one of the methods below. // For download via Maven from GitHub Packages: - implementation 'com.millicast:millicast-sdk-android:1.5.2' + implementation 'com.millicast:millicast-sdk-android:1.7.0' // For manual addition of AAR file: // implementation project(":MillicastSDK") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c0bd047..2663211 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -30,7 +29,8 @@ + android:theme="@style/Theme.Androidapp.NoActionBar" + android:exported="true"> diff --git a/app/src/main/java/com/millicast/android_app/MCTypes.java b/app/src/main/java/com/millicast/android_app/MCTypes.java index 2bd2041..8abc2fc 100644 --- a/app/src/main/java/com/millicast/android_app/MCTypes.java +++ b/app/src/main/java/com/millicast/android_app/MCTypes.java @@ -7,15 +7,15 @@ public class MCTypes { /** - * The type of {@link com.millicast.BitrateSettings} referred to. + * The type of {@link com.millicast.publishers.BitrateSettings} referred to. */ public enum Bitrate { /** - * {@link com.millicast.BitrateSettings#minBitrateKbps} + * {@link com.millicast.publishers.BitrateSettings#minBitrateKbps} */ MIN, /** - * {@link com.millicast.BitrateSettings#maxBitrateKbps} + * {@link com.millicast.publishers.BitrateSettings#maxBitrateKbps} */ MAX; } diff --git a/app/src/main/java/com/millicast/android_app/MillicastManager.java b/app/src/main/java/com/millicast/android_app/MillicastManager.java index 6a8453e..1f4aa18 100644 --- a/app/src/main/java/com/millicast/android_app/MillicastManager.java +++ b/app/src/main/java/com/millicast/android_app/MillicastManager.java @@ -12,23 +12,32 @@ import android.os.Looper; import android.util.Log; -import com.millicast.AudioPlayback; -import com.millicast.AudioSource; -import com.millicast.AudioTrack; -import com.millicast.BitrateSettings; -import com.millicast.Client; -import com.millicast.LayerData; -import com.millicast.LogLevel; -import com.millicast.Logger; +import com.millicast.Core; +import com.millicast.android_app.compat.CompatBitrateSettings; +import com.millicast.android_app.compat.CompatPublisherCredentials; +import com.millicast.android_app.compat.CompatPublisherOptions; +import com.millicast.android_app.compat.CompatSubscriberCreds; +import com.millicast.android_app.compat.CompatSubscriberOptions; +import com.millicast.android_app.compat.PublisherCompat; +import com.millicast.android_app.compat.SubscriberCompat; +import com.millicast.devices.source.video.CameraVideoSource; +import com.millicast.devices.track.Track; +import com.millicast.publishers.BitrateSettings; +import com.millicast.publishers.state.RecordingState; +import com.millicast.subscribers.ProjectionData; +import com.millicast.utils.LogLevel; +import com.millicast.utils.Logger; +import com.millicast.devices.playback.AudioPlayback; +import com.millicast.devices.source.audio.AudioSource; import com.millicast.Media; -import com.millicast.Publisher; -import com.millicast.Subscriber; -import com.millicast.Track; -import com.millicast.VideoCapabilities; import com.millicast.VideoRenderer; -import com.millicast.VideoSource; -import com.millicast.VideoTrack; import com.millicast.android_app.MCTypes.Source; +import com.millicast.devices.source.video.VideoCapabilities; +import com.millicast.devices.source.video.VideoSource; +import com.millicast.devices.track.AudioTrack; +import com.millicast.devices.track.VideoTrack; +import com.millicast.subscribers.state.LayerData; +import com.voxeet.promise.Promise; import org.webrtc.Camera1Enumerator; import org.webrtc.Camera2Enumerator; @@ -38,8 +47,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; -import static com.millicast.Source.Type.NDI; import static com.millicast.android_app.Constants.ACCOUNT_ID; import static com.millicast.android_app.Constants.ACTION_MAIN_CAMERA_CLOSE; import static com.millicast.android_app.Constants.ACTION_MAIN_CAMERA_OPEN; @@ -55,8 +65,14 @@ import static com.millicast.android_app.MCTypes.Source.CURRENT; import static com.millicast.android_app.Utils.getArrayStr; import static com.millicast.android_app.Utils.logD; +import static com.millicast.devices.type.Type.NDI; +import static com.millicast.devices.type.Type.DEVICE; + + import static org.webrtc.RendererCommon.ScalingType.SCALE_ASPECT_FIT; +import kotlin.Unit; + /** * The {@link MillicastManager} helps to manage the Millicast SDK and provides * a simple set of public APIs for common operations so that UI layer can achieve goals @@ -70,7 +86,6 @@ public class MillicastManager { public static final String TAG = "MCM"; private static MillicastManager SINGLE_INSTANCE; - private Context context; private Activity mainActivity; @@ -148,8 +163,8 @@ public class MillicastManager { private String videoSourceIndexKey = "VIDEO_SOURCE_INDEX"; private int videoSourceIndexDefault = 0; private int videoSourceIndex; - private VideoSource videoSource; - private VideoSource videoSourceSwitched; + private CameraVideoSource videoSource; + private CameraVideoSource videoSourceSwitched; private ArrayList capabilityList; private String capabilityIndexKey = "CAPABILITY_INDEX"; @@ -196,11 +211,13 @@ public class MillicastManager { private ScalingType scalingSub = SCALE_ASPECT_FIT; // Publish/Subscribe - private Publisher publisher; - private Subscriber subscriber; + private PublisherCompat publisher; + private SubscriberCompat subscriber; + + // Options objects for Publish/Subscribe - private Publisher.Option optionPub; - private Subscriber.Option optionSub; + private CompatPublisherOptions optionPub; + private CompatSubscriberOptions optionSub; // View objects private SwitchHdl switchHdl; @@ -244,12 +261,12 @@ public void init(Context context) { threadVideo.start(); handlerVideo = new Handler(threadVideo.getLooper()); - Client.initMillicastSdk(this.context); + Core.INSTANCE.initialize(); // Set Logger - Logger.setLoggerListener((String msg, LogLevel level) -> { + Logger.INSTANCE.setLoggerListener((String msg, LogLevel level) -> { String logTag = "[SDK][Log][L:" + level + "] "; LogLevel minLevel = LogLevel.MC_LOG; - if(level.ordinal() > minLevel.ordinal()){ + if (level.ordinal() > minLevel.ordinal()) { return; } logD(TAG, logTag + msg); @@ -269,9 +286,9 @@ public void init(Context context) { setCodecIndex(Utils.getSaved(videoCodecIndexKey, videoCodecIndexDefault, context), false); // Create Publisher and Subscriber Options - optionPub = new Publisher.Option(); - optionPub.stereo = true; - optionSub = new Subscriber.Option(); + optionPub = new CompatPublisherOptions(); + optionPub.setStereo(true); + optionSub = new CompatSubscriberOptions(); sourceMap = new HashMap<>(); // Set credentials from stored values if present, else from Constants file values. @@ -989,24 +1006,50 @@ public void setAudioOnly(boolean audioOnly) { } /** - * Sets RecordingEnabled for the publisher. - * If set to true, the recording will start as soon as the stream is published. - * If called after publish(), will enable recording of the active stream - * @param recordingEnabled + * Toggles the recording feature for the publisher + * If publisher has already started broadcasting, the currently published stream + * will be recorded. If there is not stream yet, a flag will be set to start recording after publishing. */ - public boolean setRecordingEnabled(boolean recordingEnabled){ - if(this.isPublishing()){ - if(recordingEnabled){ - this.recordingEnabledPub = publisher.recording.record(); - } - else { - this.recordingEnabledPub = publisher.recording.unrecord(); - } - } - else { - this.recordingEnabledPub = recordingEnabled; + public void toggleRecordingEnabled() { + String logClass = "[Pub][Rec] "; + if (publisher == null) { + logD(TAG, logClass + "Currently not publishing. Setting flag to " + !recordingEnabledPub); + recordingEnabledPub = !recordingEnabledPub; + fragmentPub.setUI(); + return; } - return this.recordingEnabledPub; + publisher.isPublishing().then(publishing -> { + if (publishing) { + if (publisher.isRecording()) { + logD(TAG, logClass + "Currently recording the published stream. Stopping..."); + publisher.stopRecording().then(success -> { + logD(TAG, logClass + "Stopped"); + }).error(e -> { + logD(TAG, logClass + "Failed! NOT stopped"); + logD(TAG, logClass + e.getLocalizedMessage()); + }); + } else { + logD(TAG, logClass + "Currently not recording the published stream. Starting..."); + publisher.startRecording().then(success -> { + logD(TAG, logClass + "Started"); + }).error(e -> { + logD(TAG, logClass + "Failed! NOT started"); + logD(TAG, logClass + e.getLocalizedMessage()); + + }); + } + } else { + logD(TAG, logClass + "Currently not publishing. Setting flag to " + !recordingEnabledPub); + recordingEnabledPub = !recordingEnabledPub; + fragmentPub.setUI(); + } + }).error(e -> { + logD(TAG, "[Rec][Error] " + e.getLocalizedMessage()); + }); + } + + public void setRecordingEnabled(boolean enabled) { + this.recordingEnabledPub = enabled; } /** @@ -1106,7 +1149,7 @@ public void setVideoEnabledSub(boolean videoEnabledSub) { public ArrayList getAudioPlaybackList() { if (audioPlaybackList == null) { - audioPlaybackList = getMedia().getAudioPlayback(); + audioPlaybackList = new ArrayList<>(getMedia().getAudioPlayback()); } String log = "[getAudioPlaybackList] AudioPlaybackList is: " + audioPlaybackList; Log.d(TAG, log); @@ -1135,8 +1178,8 @@ public boolean setAudioPlaybackIndex(int newValue) { // If currently subscribing, do not set new audioSourceIndex. if (isSubscribing()) { logD(TAG, logTag + "NOT setting to " + newValue + " as currently subscribing."); - logD(TAG, logTag + "AudioPlayback:" + - getAudioSourceStr(audioPlayback, true)); + logD(TAG, logTag + "AudioPlayback:" + ""); + getAudioPlaybackStr(audioPlayback, true); return false; } @@ -1164,7 +1207,7 @@ public VideoRenderer getRendererPub() { String logTag = "[Video][Render][er][Pub] "; // If it's not available, create it with application context. if (rendererPub == null) { - rendererPub = new VideoRenderer(context); + rendererPub = new VideoRenderer(context, null); rendererPub.setScalingType(scalingPub); logD(TAG, logTag + "Created renderer with application context."); @@ -1188,7 +1231,7 @@ public VideoRenderer getRendererSub() { String logTag = "[Video][Render][er][Sub] "; // If it's not available, create it with application context. if (rendererSub == null) { - rendererSub = new VideoRenderer(context); + rendererSub = new VideoRenderer(context, null); rendererSub.setScalingType(scalingSub); logD(TAG, logTag + "Created renderer with application context."); } else { @@ -1367,9 +1410,6 @@ public boolean isNdiOutputEnabled(boolean forAudio) { if (track == null) { logD(TAG, logTag + "Flag: " + ndiEnabled + ", Track: Does not exist."); return ndiEnabled; - } else { - boolean enabled = track.isNdiOutputEnabled(); - logD(TAG, logTag + "Flag: " + ndiEnabled + ", Track: " + enabled + "."); } return ndiEnabled; } @@ -1377,7 +1417,7 @@ public boolean isNdiOutputEnabled(boolean forAudio) { /** *

* Warning: As of SDK 1.1.1 and until documented otherwise in the SDK, - * {@link AudioTrack#enableNdiOutput enabling} and {@link AudioTrack#disableNdiOutput() disabling} + * {@link VideoTrack#enableNdiOutput enabling} and {@link VideoTrack#disableNdiOutput() disabling} * NDI output for {@link AudioTrack} is still not functional, i.e. will not have any impact on * NDI output when called. To enable audio NDI output for subscribed stream, please use * "ndi output" from the {@link #audioPlaybackList list of Audio Playback Devices}. @@ -1392,7 +1432,7 @@ public boolean isNdiOutputEnabled(boolean forAudio) { * @param sourceName If NDI output enabled, this will be the NDI source name. */ public void enableNdiOutput(boolean enable, boolean forAudio, String sourceName) { - String logTag = "[Sub][NDI]"; + /*String logTag = "[Sub][NDI]"; String log = ""; Track track = audioTrackSub; @@ -1448,6 +1488,7 @@ public void enableNdiOutput(boolean enable, boolean forAudio, String sourceName) } } logD(TAG, logTag + log); + */ } //********************************************************************************************** @@ -1473,15 +1514,15 @@ public void setBitrate(int bitrate, MCTypes.Bitrate type) { return; } - BitrateSettings settings = optionPub.bitrateSettings; + CompatBitrateSettings settings = optionPub.getBitrateSettings(); switch (type) { case MIN: logTag += "[Min] "; - settings.minBitrateKbps = Optional.of(bitrate); + settings.setMinBitrateKbps(bitrate); break; case MAX: logTag += "[Max] "; - settings.maxBitrateKbps = Optional.of(bitrate); + settings.setMaxBitrateKbps(bitrate); break; } logD(TAG, logTag + bitrate + "kbps."); @@ -1542,28 +1583,27 @@ public int getVideoCodecIndex() { public boolean setCodecIndex(int newValue, boolean forAudio) { String logTag = "[Codec][Index][Set] "; // Set new value into SharePreferences. - int oldValue = videoCodecIndex; - String key = videoCodecIndexKey; + AtomicInteger oldValue = new AtomicInteger(videoCodecIndex); + AtomicReference key = new AtomicReference<>(videoCodecIndexKey); if (forAudio) { logTag = "[Audio]" + logTag; } else { logTag = "[Video]" + logTag; } - if (isPublishing()) { - logD(TAG, logTag + "Failed! Unable to set new codec while publishing!"); + String finalLogTag = logTag; + if (pubState == PublisherState.PUBLISHING) { + logD(TAG, finalLogTag + "Failed! Unable to set new codec while publishing!"); return false; } - if (forAudio) { - oldValue = audioCodecIndex; - key = audioCodecIndexKey; + oldValue.set(audioCodecIndex); + key.set(audioCodecIndexKey); audioCodecIndex = newValue; } else { videoCodecIndex = newValue; } - - Utils.setSaved(key, newValue, context); - logD(TAG, logTag + "Now: " + newValue + + Utils.setSaved(key.get(), newValue, context); + logD(TAG, finalLogTag + "Now: " + newValue + " Was: " + oldValue); // Set new codec @@ -1619,23 +1659,30 @@ public void connectPub() { return; } - if (publisher.isConnected()) { - logD(TAG, logTag + "Not doing as we're already connected!"); - return; - } - - setPubState(PublisherState.CONNECTING); + publisher.isConnected() + .then(connected -> { + if (connected) { + logD(TAG, logTag + "Not doing as we're already connected!"); + } else { + setPubState(PublisherState.CONNECTING); + logD(TAG, logTag + "Trying..."); + // Connect Publisher. + connectPubMc().then(success -> { + if (success) { + logD(TAG, logTag + "OK."); + } else { + setPubState(PublisherState.DISCONNECTED); + logD(TAG, logTag + "Failed! Connection requirements not fulfilled. Check inputs (e.g. credentials) and any Millicast error message."); + } + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); + } + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); - // Connect Publisher. - logD(TAG, logTag + "Trying..."); - boolean success = connectPubMc(); - if (success) { - logD(TAG, logTag + "OK."); - } else { - setPubState(PublisherState.DISCONNECTED); - logD(TAG, logTag + "Failed! Connection requirements not fulfilled. Check inputs (e.g. credentials) and any Millicast error message."); - } } /** @@ -1652,25 +1699,22 @@ public void connectSub() { return; } - if (subscriber.isConnected()) { - logD(TAG, logTag + "Not doing as we're already connected!"); - return; - } + subscriber.isConnected().then(connected -> { + if (connected) { + logD(TAG, logTag + "Not doing as we're already connected!"); + return; + } - setSubState(SubscriberState.CONNECTING); + setSubState(SubscriberState.CONNECTING); - // Connect Subscriber. - logD(TAG, logTag + "Trying..."); - boolean success = connectSubMc(); + // Connect Subscriber. + logD(TAG, logTag + "Trying..."); + connectSubMc(); - if (success) { - logD(TAG, logTag + "OK."); - logD(TAG, logTag + "Initializing audio playback device..."); - audioPlaybackStart(); - } else { - setSubState(SubscriberState.DISCONNECTED); - logD(TAG, logTag + "Failed! Connection requirements not fulfilled. Check inputs (e.g. credentials) and any Millicast error message."); - } + + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); } @@ -1690,45 +1734,32 @@ public void startPub() { return; } - if (!publisher.isConnected()) { - if (pubState == PublisherState.CONNECTED) { - logD(TAG, logTag + "Client.isConnected FALSE!!! " + - "Continuing as pubState is " + pubState + "."); - } else { - logD(TAG, logTag + "Failed! Publisher not connected!" + - " pubState is " + pubState + "."); - return; - } - } - - if (isPublishing()) { - logD(TAG, logTag + "Not publishing as we are already publishing!"); - return; - } - - if (!isAudioVideoCaptured()) { - logD(TAG, logTag + "Failed! Both audio & video are not captured."); - return; - } - - // Publish to Millicast - logD(TAG, logTag + "Trying..."); - - setRecordingEventHandlers(); - boolean success = startPubMc(); - - if (success) { - logD(TAG, logTag + "Starting publish..."); - } else { - logD(TAG, logTag + "Failed! Start publish requirements not fulfilled. Check current states and any Millicast error message."); - return; - } - - // Get Publisher stats every 10 seconds. - int sec = 10; - enableStatsPub(sec * 1000); - logD(TAG, logTag + "Stats started. Collecting every " + sec + "."); - logD(TAG, logTag + "OK."); + publisher.isConnected() + .then(connected -> { + if (!connected) { + logD(TAG, logTag + "Not publishing as we are not connected!"); + return; + } + publisher.isPublishing() + .then(publishing -> { + if (publishing) { + logD(TAG, logTag + "Not publishing as we are already publishing!"); + return; + } + if (!isAudioVideoCaptured()) { + logD(TAG, logTag + "Failed! Both audio & video are not captured."); + return; + } + logD(TAG, logTag + "Trying..."); + startPubMc(); + }) + .error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); + + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); } /** @@ -1737,51 +1768,20 @@ public void startPub() { */ public void stopPub() { String logTag = "[Pub][Stop] "; - if (!isPublishing()) { - logD(TAG, logTag + "Not doing as we're not publishing!"); - return; - } - - // Stop publishing - logD(TAG, logTag + "Trying to stop publish..."); - boolean success = stopPubMc(); - - if (success) { - logD(TAG, logTag + "Publish stopped and we have disconnected."); - setPubState(PublisherState.DISCONNECTED); - } else { - logD(TAG, logTag + "Failed! Stop publishing requirements not fulfilled. Check current states and any Millicast error message."); - return; - } - - enableStatsPub(0); - logD(TAG, logTag + "Stats stopped"); - - // Remove Publisher. - publisher = null; - logD(TAG, logTag + "Publisher removed."); - logD(TAG, logTag + "OK."); - } - - private void setRecordingEventHandlers(){ - String logTag = "[Pub][Rec] "; - - publisher.recording.onRecordingStarted(() -> { - logD(TAG, logTag + "Recording started."); - }); - publisher.recording.onRecordingStopped(() -> { - logD(TAG, logTag + "Recording stopped."); - }); - publisher.recording.onFailedToStartRecording(() -> { - logD(TAG, logTag + "Failed to start recording."); - }); - publisher.recording.onFailedToStopRecording(() -> { - logD(TAG, logTag + "Failed to stop recording."); + publisher.isPublishing().then(publishing -> { + if (!publishing) { + logD(TAG, logTag + "Not doing as we're not publishing!"); + return; + } + // Stop publishing + logD(TAG, logTag + "Trying to stop publish..."); + stopPubMc(); + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); }); - - } + public PublishFragment getFragmentPub() { return fragmentPub; } @@ -1822,38 +1822,31 @@ public void startSub() { return; } - if (!subscriber.isConnected()) { - if (subState == SubscriberState.CONNECTED) { - logD(TAG, logTag + "Client.isConnected FALSE!!! " + - "Continuing as subState is " + subState + "."); - } else { - logD(TAG, logTag + "Failed! Subscriber not connected!" + - " subState is " + subState + "."); + subscriber.isConnected().then(connected -> { + if (!connected) { + logD(TAG, logTag + "Failed! Subscriber not connected!" + " subState is " + subState + "."); + return; + } + if (isSubscribing()) { + logD(TAG, logTag + "Not subscribing as we are already subscribing!"); return; } - } - - if (isSubscribing()) { - logD(TAG, logTag + "Not subscribing as we are already subscribing!"); - return; - } - - // Subscribe to Millicast - logD(TAG, logTag + "Trying..."); - boolean success = startSubMc(); - - if (success) { - logD(TAG, logTag + "Starting subscribe..."); - } else { - logD(TAG, logTag + "Failed! Start subscribe requirements not fulfilled. Check current states and any Millicast error message."); - return; - } - // Get Subscriber stats every 10 seconds. - int sec = 10; - enableStatsSub(sec * 1000); - logD(TAG, logTag + "Stats started. Collecting every " + sec + "."); - logD(TAG, logTag + "OK."); + // Subscribe to Millicast + logD(TAG, logTag + "Trying..."); + startSubMc().then(success -> { + logD(TAG, logTag + "OK. Starting subscribe to Millicast."); + // Enable Subscriber stats + logD(TAG, logTag + "Enabling stats..."); + enableStatsSub(true); + logD(TAG, logTag + "OK."); + }).error(e -> { + logD(TAG, logTag + "Failed! Check current states and any Millicast error message."); + logD(TAG, logTag + "Failed!" + e.getLocalizedMessage()); + }); + }).error(e -> { + logD(TAG, logTag + "Failed!" + e.getLocalizedMessage()); + }); } /** @@ -1869,17 +1862,9 @@ public void stopSub() { // Stop subscribing logD(TAG, logTag + "Trying to stop subscribe..."); - boolean success = stopSubMc(); + stopSubMc(); - if (success) { - logD(TAG, logTag + "Subscribe stopped and we have disconnected."); - setSubState(SubscriberState.DISCONNECTED); - } else { - logD(TAG, logTag + "Failed! Stop subscribing requirements not fulfilled. Check current states and any Millicast error message."); - return; - } - - enableStatsSub(0); + enableStatsSub(false); logD(TAG, logTag + "Stats stopped."); // Remove Subscriber. @@ -1986,6 +1971,7 @@ public SourceInfo getSourceProjected(boolean isAudio) { */ public boolean projectSource(String sourceId, boolean isAudio) { String logTag = "[Source][Id][Project]:" + sourceId + " "; + Promise rval; if (isAudio) { logTag += "A "; } else { @@ -2022,7 +2008,7 @@ public boolean projectSource(String sourceId, boolean isAudio) { } // Generate the ProjectionData required from the MediaInfo. - ArrayList projectionData = + ArrayList projectionData = sourceInfo.getProjectionData(mid, isAudio); if (projectionData == null) { log = "Failed! ProjectData is not available for sourceInfo! " + @@ -2034,29 +2020,29 @@ public boolean projectSource(String sourceId, boolean isAudio) { // Send the request to Millicast and return the result of the request. log = "Trying to project source onto mid:" + mid + " ..."; logD(TAG, logTag + log); - boolean result = subscriber.project(sourceId, projectionData); - - // If successful, track the new sourceId. - if (result) { - log = "OK. Projected new "; + String finalLogTag = logTag; + subscriber.project(sourceId, projectionData).then(success -> { + String log2 = "[Sub][Project]"; + // If successful, track the new sourceId. + log2 += "OK. Projected new "; if (isAudio) { - log += "Audio source.\n"; + log2 += "Audio source.\n"; sourceIdAudioSub = sourceId; - logD(TAG, logTag + log); + logD(TAG, finalLogTag + log2); } else { - log += "Video source.\n"; + log2 += "Video source.\n"; sourceIdVideoSub = sourceId; // Set view to display the Layers of the projected video source. - log += "Setting new Layers of the project source in the view..."; - logD(TAG, logTag + log); + log2 += "Setting new Layers of the project source in the view..."; + logD(TAG, finalLogTag + log2); // Reset the Layers UI in the view. loadViewSubLayer(); } - } else { - log = "Failed!"; - logD(TAG, logTag + log); - } - return result; + }).error(e -> { + logD(TAG, finalLogTag + "Failed! "); + logD(TAG, finalLogTag + e.getLocalizedMessage()); + }); + return true; } /** @@ -2291,49 +2277,56 @@ public Optional getLayerData() { * @param layerId * @return True if the Layer of the layerId was (or already) selected, false otherwise. */ - public boolean selectLayer(String layerId) { - String logTag = "[Layer][Select]:" + layerId + " "; - String log; + public Promise selectLayer(String layerId) { + return new Promise<>((resolve, reject) -> { + String logTag = "[Layer][Select]:" + layerId + " "; + String log; - if (layerId == null) { - logD(TAG, logTag + "Failed! LayerId invalid."); - return false; - } + if (layerId == null) { + logD(TAG, logTag + "Failed! LayerId invalid."); + reject.call(new Throwable("LayerId invalid.")); - SourceInfo source = getSourceProjected(false); - if (source == null) { - logD(TAG, logTag + "Failed! No video source set."); - return false; - } + } - // Check if already selected. - String currentLayerId = getLayerActiveId(); - if (layerId.equals(currentLayerId)) { - log = "OK. Already selected, not selecting again."; - logD(TAG, logTag + log); - return true; - } + SourceInfo source = getSourceProjected(false); + if (source == null) { + logD(TAG, logTag + "Failed! No video source set."); + reject.call(new Throwable("No video source set.")); + } - // Get the layerData that we should select. - Optional layerData = source.getLayerData(layerId); - if (layerData == null) { - logD(TAG, logTag + "Failed! Unable to get Layer."); - return false; - } + // Check if already selected. + String currentLayerId = getLayerActiveId(); + if (layerId.equals(currentLayerId)) { + log = "OK. Already selected, not selecting again."; + logD(TAG, logTag + log); + reject.call(new Throwable("Already selected, not selecting again.")); + } - // Perform layer selection and return the result. - if (subscriber.select(layerData)) { - logD(TAG, logTag + "OK."); - if (source.setLayerActiveId(layerId)) { - logD(TAG, logTag + "Set new layer active layerId."); - } else { - logD(TAG, logTag + "Error! Failed to set new layer active layerId!"); + // Get the layerData that we should select. + LayerData layerData = source.getLayerData(layerId).get(); + if (layerData == null) { + logD(TAG, logTag + "Failed! Unable to get Layer."); + reject.call(new Throwable("Unable to get Layer.")); } - return true; - } else { - logD(TAG, logTag + "Failed! Could not select layer!"); - return false; - } + + // Perform layer selection and return the result. + subscriber.select(layerData) + .then(success -> { + logD(TAG, logTag + "OK."); + if (source.setLayerActiveId(layerId)) { + logD(TAG, logTag + "Set new layer active layerId."); + resolve.call(true); + } else { + logD(TAG, logTag + "Error! Failed to set new layer active layerId!"); + reject.call(new Throwable("Failed to set new layer active layerId!")); + } + }).error(e -> { + logD(TAG, logTag + "Failed! Could not select layer!"); + logD(TAG, logTag + e.getLocalizedMessage()); + reject.call(e); + }); + }); + } /** @@ -2400,30 +2393,31 @@ public int getSelectedIndex(ArrayList list) { * @param isAudio * @return */ - public boolean addMediaTrack(boolean isAudio) { - String kind = "audio"; - if (!isAudio) { - kind = "video"; - } - return this.subscriber.addRemoteTrack(kind); - } - /** - * Get the Mid of a Subscribed track by the given trackId. - * The trackId can be obtained by calling {@link Track#getName()}. - * - * @param trackId - * @return - */ - public String getTrackMidSub(String trackId) { - return this.subscriber.getMid(trackId).get(); - } + //TODO clean up +// public boolean addMediaTrack(boolean isAudio) { +// TrackType kind = TrackType.Audio; +// if (!isAudio) { +// kind = TrackType.Video; +// } +// return this.subscriber.addRemoteTrack(kind); +// } +// +// /** +// * Get the Mid of a Subscribed track by the given trackId. +// * The trackId can be obtained by calling {@link Track#getName()}. +// * +// * @param trackId +// * @return +// */ +// public String getTrackMidSub(String trackId) { +// return this.subscriber.getMid(trackId); +// } //********************************************************************************************** // Utilities //********************************************************************************************** - public Context getContext() { return context; } @@ -2534,19 +2528,14 @@ public String getCodecName(boolean forAudio) { * Enable or disable Publisher's WebRTC stats. * Stats are only collected after Publisher is connected to Millicast. * - * @param enable The interval in ms between stats reports. - * Set to 0 to disable stats. + * @param enable */ - public void enableStatsPub(int enable) { + public void enableStatsPub(boolean enable) { if (publisher != null) { String logTag = "[Pub][Stats][Enable] "; - if (enable > 0) { - publisher.getStats(enable); - logD(TAG, logTag + "YES. Interval: " + enable + "ms."); - } else { - publisher.getStats(0); - logD(TAG, logTag + "NO."); - } + publisher.getStats(enable); + logD(TAG, logTag + enable); + } } @@ -2554,19 +2543,14 @@ public void enableStatsPub(int enable) { * Enable or disable Subscriber's WebRTC stats. * Stats are only collected after Subscriber is connected to Millicast. * - * @param enable The interval in ms between stats reports. - * Set to 0 to disable stats. + * @param enable */ - public void enableStatsSub(int enable) { + public void enableStatsSub(boolean enable) { if (subscriber != null) { String logTag = "[Sub][Stats][Enable] "; - if (enable > 0) { - subscriber.getStats(enable); - logD(TAG, logTag + "YES. Interval: " + enable + "ms."); - } else { - subscriber.getStats(0); - logD(TAG, logTag + "NO."); - } + subscriber.getStats(enable); + logD(TAG, logTag + enable); + } } @@ -2625,7 +2609,7 @@ private SwitchHdl getSwitchHdl() { public boolean setCameraParams(String shootMode) { boolean result = true; try { - VideoSource videoSource = MillicastManager.getSingleInstance().getVideoSource(false); + CameraVideoSource videoSource = MillicastManager.getSingleInstance().getVideoSource(false); Camera.Parameters parameters = videoSource.getParameters(); Log.d(TAG, "setCameraParams: setting " + shootMode + "... "); @@ -2737,7 +2721,7 @@ public void setMainActivity(Activity mainActivity) { private Media getMedia() { if (media == null) { - media = Media.getInstance(context); + media = Media.INSTANCE; } return media; } @@ -2761,7 +2745,7 @@ public void run() { logD(TAG, logTag + "Getting new audioSources."); // Get new audioSources. - audioSourceList = getMedia().getAudioSources(); + audioSourceList = (ArrayList) getMedia().getAudioSources(); // Print out list of audioSources. logD(TAG, logTag + "Checking for audioSources..."); @@ -2891,7 +2875,7 @@ public void run() { logD(TAG, logTag + "Getting new videoSources."); // Get new videoSources. - videoSourceList = getMedia().getVideoSources(); + videoSourceList = new ArrayList<>(getMedia().getVideoSources()); // Print out list of videoSources. logD(TAG, logTag + "Checking for videoSources..."); @@ -2941,7 +2925,7 @@ public void run() { * * @param getSwitched If true, return videoSourceSwitched instead. */ - private VideoSource getVideoSource(boolean getSwitched) { + private CameraVideoSource getVideoSource(boolean getSwitched) { String logTag = "[Source][Video][Get] "; if (getSwitched) { @@ -2973,7 +2957,7 @@ private void setVideoSource() { String logTag = "[Source][Video][Set] "; // Create new videoSource based on index. - VideoSource videoSourceNew; + CameraVideoSource videoSourceNew; if (videoSourceList == null) { logD(TAG, logTag + "Failed as no valid videoSource was available!"); @@ -3000,7 +2984,7 @@ private void setVideoSource() { return; } - videoSourceNew = videoSourceList.get(videoSourceIndex); + videoSourceNew = (CameraVideoSource) videoSourceList.get(videoSourceIndex); String log; if (videoSourceNew != null) { @@ -3056,7 +3040,7 @@ private void setCapabilityList() { String logTag = "[Capability][List][Set] "; String log = logTag; - VideoSource vs = null; + CameraVideoSource vs = null; if (videoSourceSwitched != null) { vs = videoSourceSwitched; log += "From videoSourceSwitched."; @@ -3068,7 +3052,7 @@ private void setCapabilityList() { return; } - capabilityList = vs.getCapabilities(); + capabilityList = (ArrayList) vs.getCapabilities(); logD(TAG, log); int size = 0; @@ -3222,16 +3206,14 @@ private Integer videoSourceIndexNextNonNdi(boolean ascending, int curIndex, Arra int now = curIndex; int next = videoSourceIndexNext(ascending, now, size); // Keep searching for the next non-NDI until - while (videoSourceList.get(next).getType() == NDI) { - if (next == now) { - logD(TAG, logTag + "Failed! 1 complete cycle done."); - return null; + while (videoSourceList.get(next).getType() == DEVICE) { + if (next != now) { + return next; } - now = next; - next = videoSourceIndexNext(ascending, now, size); + next = videoSourceIndexNext(ascending, next, size); } logD(TAG, logTag + next + " Failed! 1 complete cycle done."); - return next; + return null; } /** @@ -3410,7 +3392,7 @@ private void startCaptureVideo() { " with Cap: " + getCapabilityStr(capability) + "."); videoSource.setEventsHandler(getVideoSourceEvtHdl()); - VideoTrack videoTrack = (VideoTrack) videoSource.startCapture(); + VideoTrack videoTrack = videoSource.startCapture(); if (videoSource.getType() == NDI) { setCapState(CaptureState.IS_CAPTURED); @@ -3576,7 +3558,7 @@ private void setAudioPlayback() { String log; if (audioPlaybackNew != null) { - log = getAudioSourceStr(audioPlaybackNew, true); + log = getAudioPlaybackStr(audioPlaybackNew, true); } else { log = "None"; } @@ -3720,7 +3702,7 @@ private void mirrorFrontCamera() { } CameraEnumerator cameraEnumerator; String name = getVideoSource(true).getName(); - if (Media.isCamera2Supported(context)) { + if (Media.INSTANCE.isCamera2Supported()) { cameraEnumerator = new Camera2Enumerator(context); } else { cameraEnumerator = new Camera1Enumerator(); @@ -3741,13 +3723,13 @@ private void mirrorFrontCamera() { /** * Set the current audio/videoCodec at the audio/videoCodecIndex of the current * audio/videoCodecList, - * and in the {@link Publisher.Option} as preferred codecs if available and NOT currently publishing. + * and in the {@link com.millicast.publishers.Option} as preferred codecs if available and NOT currently publishing. */ private void setCodecs() { String logTag = "[Codec][Set] "; final String none = "None"; - String ac = none; - String vc = none; + String ac; + String vc; getCodecList(true); getCodecList(false); @@ -3755,6 +3737,7 @@ private void setCodecs() { logD(TAG, logTag + "Selecting a new one based on selected index."); if (audioCodecList == null || audioCodecList.size() < 1) { + ac = none; logD(TAG, logTag + "Failed to set audio codec as none was available!"); } else { int size = audioCodecList.size(); @@ -3774,6 +3757,7 @@ private void setCodecs() { } if (videoCodecList == null || videoCodecList.size() < 1) { + vc = none; logD(TAG, logTag + "Failed to set video codec as none was available!"); } else { int size = videoCodecList.size(); @@ -3796,33 +3780,40 @@ private void setCodecs() { logD(TAG, logTag + "Selected at index:" + audioCodecIndex + "/" + videoCodecIndex + " is: " + ac + "/" + vc + "."); - String log = logTag + "OK. "; + StringBuilder sb = new StringBuilder(); + sb.append(logTag + "OK. "); if (publisher != null) { - if (!publisher.isPublishing()) { - if (!none.equals(ac)) { - audioCodec = ac; - optionPub.audioCodec = Optional.of(ac); - log += "Set preferred Audio:" + audioCodec + " on Publisher. "; - } else { - log += "Audio NOT set on Publisher."; - } - if (!none.equals(vc)) { - videoCodec = vc; - optionPub.videoCodec = Optional.of(vc); - log += "Set preferred Video:" + videoCodec + " on Publisher."; + publisher.isPublishing().then(publishing -> { + if (!publishing) { + if (!none.equals(ac)) { + audioCodec = ac; + optionPub.setAudioCodec(ac); + sb.append("Set preferred Audio:" + audioCodec + " on Publisher. "); + } else { + sb.append("Audio NOT set on Publisher."); + } + if (!none.equals(vc)) { + videoCodec = vc; + optionPub.setVideoCodec(vc); + sb.append("Set preferred Video:" + videoCodec + " on Publisher."); + } else { + sb.append("Video NOT set on Publisher."); + } + } else { - log += "Video NOT set on Publisher."; + sb.append("NOT set, as publishing is ongoing: "); } - - } else { - log += "NOT set, as publishing is ongoing: "; - } + logD(TAG, sb.toString()); + }).error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); } else { - log += "NOT set, as publisher does not exists: "; + sb.append("NOT set, as publisher does not exists: "); audioCodec = ac; videoCodec = vc; + logD(TAG, sb.toString()); } - logD(TAG, log); + } /** @@ -3865,79 +3856,78 @@ private Integer codecIndexNext(boolean ascending, boolean forAudio) { * Millicast methods to connect to Millicast for publishing. * Publishing credentials required. * If connecting requirements are met, will return true and trigger SDK to start connecting to Millicast. Otherwise, will return false. - * Actual connection success will be reported by {@link Publisher.Listener#onConnected()}. - */ - private boolean connectPubMc() { - String logTag = "[Pub][Con][Mc] "; - - // Set Credentials - Publisher.Credential creds = publisher.getCredentials(); - creds.apiUrl = getUrlPub(CURRENT); - creds.streamName = getStreamNamePub(CURRENT); - creds.token = getTokenPub(CURRENT); - publisher.setCredentials(creds); - logD(TAG, logTag + "Set Credentials."); - - // Connect Publisher to Millicast. - boolean success = false; - String error = logTag + "Failed!"; - try { - success = publisher.connect(); - } catch (Exception e) { - error += " Error: " + e.getLocalizedMessage(); - logD(TAG, error); - } + * Actual connection success will be reported by {@link PubListener#onConnected()}. + */ + private Promise connectPubMc() { + return new Promise<>((resolve, reject) -> { + String logTag = "[Pub][Con][Mc] "; + + // Set Credentials + CompatPublisherCredentials creds = publisher.getCredentials(); + creds.setApiUrl(getUrlPub(CURRENT)); + creds.setStreamName(getStreamNamePub(CURRENT)); + creds.setToken(getTokenPub(CURRENT)); + publisher.setCredentials(creds); + logD(TAG, logTag + "Set Credentials."); + + // Connect Publisher to Millicast. + getPublisher().connect().then(success -> { + logD(TAG, logTag + "OK. Connecting to Millicast."); + resolve.call(true); + }).error(e -> { + logD(TAG, logTag + "Failed. Not Connecting to Millicast."); + logD(TAG, "Failed! Error: " + e.getLocalizedMessage()); + reject.call(e); + }); + }); - if (success) { - logD(TAG, logTag + "OK. Connecting to Millicast."); - } else { - logD(TAG, error); - } - return success; } /** * Millicast methods to connect to Millicast for subscribing. * Subscribing credentials required. * If connecting requirements are met, will return true and trigger SDK to start connecting to Millicast. Otherwise, will return false. - * Actual connection success will be reported by {@link Subscriber.Listener#onConnected()}. + * Actual connection success will be reported by {@link com.millicast.subscribers.SubscriberListener#onConnected()}. */ - private boolean connectSubMc() { + private void connectSubMc() { String logTag = "[Sub][Con][Mc] "; // Set Credentials - Subscriber.Credential creds = subscriber.getCredentials(); - creds.accountId = getAccountId(CURRENT); - creds.streamName = getStreamNameSub(CURRENT); - creds.apiUrl = getUrlSub(CURRENT); + CompatSubscriberCreds creds = subscriber.getCredentials(); + creds.setAccountId(getAccountId(CURRENT)); + creds.setStreamName(getStreamNameSub(CURRENT)); + creds.setApiUrl(getUrlSub(CURRENT)); // If a Subscribe Token is required, add it. // If it is not required: // - There is no need to add it. // - Adding a valid Subscribe Token is harmless. String tokenSub = getTokenSub(CURRENT); if (tokenSub != null && !tokenSub.isEmpty()) { - creds.token = Optional.of(tokenSub); + creds.setToken(tokenSub); logD(TAG, logTag + "Added Subscribe Token."); } subscriber.setCredentials(creds); logD(TAG, logTag + "Set Credentials."); // Connect Subscriber to Millicast. - boolean success = false; - String error = logTag + "Failed!"; + StringBuilder error = new StringBuilder(); + error.append(logTag + "Failed!"); try { - success = subscriber.connect(); + getSubscriber().connect().then(success -> { + logD(TAG, logTag + "OK. Connecting to Millicast."); + logD(TAG, logTag + "Initializing audio playback device..."); + audioPlaybackStart(); + }).error(e -> { + setSubState(SubscriberState.DISCONNECTED); + logD(TAG, logTag + "Failed! Connection requirements not fulfilled. Check inputs (e.g. credentials) and any Millicast error message."); + error.append(" Error: " + e.getLocalizedMessage()); + logD(TAG, logTag + error.toString()); + + }); } catch (Exception e) { - error += " Error: " + e.getLocalizedMessage(); - logD(TAG, error); - } + logD(TAG, error.toString() + e.getLocalizedMessage()); - if (success) { - logD(TAG, logTag + "OK. Connecting to Millicast."); - } else { - logD(TAG, error); } - return success; } //********************************************************************************************** @@ -3966,14 +3956,14 @@ private PubListener getListenerPub() { * * @return */ - private Publisher getPublisher() { + private PublisherCompat getPublisher() { if (publisher != null) { logD(TAG, "[getPublisher] Returning existing Publisher."); return publisher; } logD(TAG, "[getPublisher] Trying to create one..."); - publisher = Publisher.createPublisher(getListenerPub()); + publisher = new PublisherCompat(getListenerPub()); logD(TAG, "[getPublisher] Created and returning a new Publisher."); return publisher; @@ -3982,31 +3972,37 @@ private Publisher getPublisher() { /** * Millicast methods to start publishing. * Audio and video tracks that are already captured will be added to Publisher. - * {@link Publisher.Option} (including preferred codecs) will be set into Publisher. + * {@link com.millicast.publishers.Option} (including preferred codecs) will be set into Publisher. * If publishing requirements are met, will return true and trigger SDK to start publish. Otherwise, will return false. - * Actual publishing success will be reported by {@link Publisher.Listener#onPublishing()}. + * Actual publishing success will be reported by {@link PubListener}. */ - private boolean startPubMc() { + private void startPubMc() { String logTag = "[Pub][Start][Mc] "; if (audioTrackPub != null) { - publisher.addTrack(audioTrackPub); - logD(TAG, logTag + "Audio track added."); + publisher.addTrack(audioTrackPub).then(success -> { + logD(TAG, logTag + "Audio track added."); + }).error(e -> { + logD(TAG, logTag + "Failed! Audio NOT track added."); + }); + } else { logD(TAG, logTag + "Audio track NOT added as it does not exist."); } if (videoTrackPub != null) { - publisher.addTrack(videoTrackPub); - logD(TAG, logTag + "Video track added."); + publisher.addTrack(videoTrackPub).then(success -> { + logD(TAG, logTag + "Video track added."); + }).error(e -> { + logD(TAG, logTag + "Failed! Video NOT track added."); + }); } else { logD(TAG, logTag + "Video track NOT added as it does not exist."); } // Set Publisher Options - Optional sourceIdPub = getOptSourceIdPub(CURRENT); - optionPub.sourceId = sourceIdPub; - optionPub.recordStream = Optional.of(this.recordingEnabledPub); + optionPub.setSourceId(getOptSourceIdPub(CURRENT).toString()); + optionPub.setRecordStream(this.recordingEnabledPub); logD(TAG, logTag + "SourceId (" + sourceIdPub + ") set in Option."); setCodecs(); @@ -4020,43 +4016,56 @@ private boolean startPubMc() { logD(TAG, logTag + "Options set in Publisher."); // Publish to Millicast - boolean success = publisher.publish(); - if (success) { - logD(TAG, logTag + "OK. Starting publish to Millicast."); - } else { - logD(TAG, logTag + "Failed! Check current states and any Millicast error message."); - } - return success; + + publisher.publish() + .then(success -> { + logD(TAG, logTag + "OK. Starting publish to Millicast."); + // Enable Publisher Stats + enableStatsPub(true); + logD(TAG, logTag + "OK."); + }) + .error(e -> { + logD(TAG, logTag + e.getLocalizedMessage()); + }); } /** * Millicast methods to stop publishing. */ - private boolean stopPubMc() { + private void stopPubMc() { String logTag = "[Pub][Stop][Mc] "; // Stop publishing to Millicast. - boolean success = publisher.unpublish(); - if (success) { - logD(TAG, logTag + "OK. Stopped publishing to Millicast."); - } else { - logD(TAG, logTag + "Failed!"); - } - return success; + publisher.unpublish() + .then(success -> { + logD(TAG, logTag + "OK. Stopped publishing to Millicast."); + enableStatsPub(false); + logD(TAG, logTag + "Stats stopped"); + setPubState(PublisherState.DISCONNECTED); + fragmentPub.setUI(); + // Remove Publisher. + publisher = null; + logD(TAG, logTag + "Publisher removed."); + logD(TAG, logTag + "OK."); + }) + .error(e -> { + logD(TAG, logTag + "Failed! Stop publishing requirements not fulfilled. Check current states and any Millicast error message."); + logD(TAG, logTag + e.getLocalizedMessage()); + }); } /** * Check if we are currently publishing. */ - private boolean isPublishing() { - String logTag = "[Pub][?] "; - if (publisher == null || !publisher.isPublishing()) { - logD(TAG, logTag + "No!"); - return false; - } - logD(TAG, logTag + "Yes."); - return true; - } +// private boolean isPublishing() { +// String logTag = "[Pub][?] "; +// if (publisher == null || getPubState() != PublisherState.PUBLISHING) { +// logD(TAG, logTag + "No!"); +// return false; +// } +// logD(TAG, logTag + "Yes."); +// return true; +// } //********************************************************************************************** // Subscribe @@ -4084,25 +4093,25 @@ private SubListener getListenerSub() { * * @return */ - private Subscriber getSubscriber() { + private SubscriberCompat getSubscriber() { if (subscriber != null) { logD(TAG, "[getSubscriber] Returning existing Subscriber."); return subscriber; } logD(TAG, "[getSubscriber] Trying to create one..."); - subscriber = Subscriber.createSubscriber(getListenerSub()); + subscriber = new SubscriberCompat(getListenerSub()); logD(TAG, "[getSubscriber] Created and returning a new Subscriber."); return subscriber; } /** * Millicast methods to start subscribing. - * {@link Subscriber.Option} will be set into Subscriber. + * {@link com.millicast.subscribers.Option} will be set into Subscriber. * If subscribing requirements are met, will return true and trigger SDK to start subscribe. Otherwise, will return false. - * Actual subscribing success will be reported by {@link Subscriber.Listener#onSubscribed()}. + * Actual subscribing success will be reported by {@link SubListener#onConnected()}. */ - private boolean startSubMc() { + private Promise startSubMc() { String logTag = "[Pub][Start][Mc] "; // Set Subscriber Options @@ -4110,29 +4119,23 @@ private boolean startSubMc() { logD(TAG, logTag + "Options set."); // Subscribe to Millicast - boolean success = subscriber.subscribe(); - if (success) { - logD(TAG, logTag + "OK. Starting subscribe to Millicast."); - } else { - logD(TAG, logTag + "Failed! Check current states and any Millicast error message."); - } - return success; + logD(TAG, logTag + "Starting subscribe..."); + return subscriber.subscribe(); } /** * Millicast methods to stop subscribing. */ - private boolean stopSubMc() { + private void stopSubMc() { String logTag = "[Sub][Stop][Mc] "; // Stop subscribing to Millicast. - boolean success = subscriber.unsubscribe(); - if (success) { + subscriber.unsubscribe().then(success -> { logD(TAG, logTag + "OK. Stopped subscribing to Millicast."); - } else { + }).error(e -> { logD(TAG, logTag + "Failed!"); - } - return success; + logD(TAG, logTag + e.getLocalizedMessage()); + }); } /** @@ -4155,7 +4158,7 @@ private boolean isSubscribing() { /** * Get a String that describes a MCVideoSource. */ - private String getAudioSourceStr(com.millicast.Source audioSource, boolean longForm) { + private String getAudioSourceStr(AudioSource audioSource, boolean longForm) { String name = "Audio:"; if (audioSource == null) { name += "NULL!"; @@ -4169,6 +4172,20 @@ private String getAudioSourceStr(com.millicast.Source audioSource, boolean longF return name; } + private String getAudioPlaybackStr(AudioPlayback audioPlayback, boolean longForm) { + String name = "Audio:"; + if (audioPlayback == null) { + name += "NULL!"; + return name; + } + + name = "Audio:" + audioPlayback.getName(); + if (longForm) { + name += " (" + audioPlayback.getType() + ") " + "id:" + audioPlayback.getId(); + } + return name; + } + /** * Get a String that describes a MCVideoSource. */ @@ -4195,12 +4212,12 @@ private String getCapabilityStr(VideoCapabilities cap) { name = "Cap: N.A."; } else { // Note: FPS given in frames per 1000 seconds (FPKS). - name = cap.width + "x" + cap.height + " fps:" + cap.fps / 1000; + name = cap.getWidth() + "x" + cap.getHeight() + " fps:" + cap.getFps() / 1000; } return name; } - public boolean isRecordingEnabledPub(){ + public boolean isRecordingEnabledPub() { return recordingEnabledPub; } diff --git a/app/src/main/java/com/millicast/android_app/PubListener.java b/app/src/main/java/com/millicast/android_app/PubListener.java index 30f4763..a6bd7e9 100644 --- a/app/src/main/java/com/millicast/android_app/PubListener.java +++ b/app/src/main/java/com/millicast/android_app/PubListener.java @@ -1,6 +1,15 @@ package com.millicast.android_app; import com.millicast.Publisher; +import com.millicast.clients.stats.AudioSource; +import com.millicast.clients.stats.Codecs; +import com.millicast.clients.stats.InboundRtpStream; +import com.millicast.clients.stats.OutboundRtpStream; +import com.millicast.clients.stats.RemoteInboundRtpStream; +import com.millicast.clients.stats.RemoteOutboundRtpStream; +import com.millicast.clients.stats.RtsReport; +import com.millicast.clients.stats.VideoSource; +import com.millicast.publishers.listener.PublisherListener; import org.webrtc.RTCStatsReport; @@ -10,11 +19,15 @@ import static com.millicast.android_app.Utils.logD; import static com.millicast.android_app.Utils.makeSnackbar; +import androidx.annotation.NonNull; + +import java.util.ArrayList; + /** * Implementation of Publisher's Listener. * This handles events sent to the Publisher being listened to. */ -public class PubListener implements Publisher.Listener { +public class PubListener implements PublisherListener { public static final String TAG = "PubListener"; private MillicastManager mcMan; @@ -50,6 +63,9 @@ public void onConnected() { @Override public void onDisconnected() { String logTag = logTagClass + "[Con][On][X] "; + logD(TAG, logTag + "Publish stopped and we have disconnected."); + mcMan.setPubState(MCStates.PublisherState.DISCONNECTED); + mcMan.getFragmentPub().setUI(); makeSnackbar(logTag, "Disconnected", mcMan.getFragmentPub()); } @@ -67,11 +83,29 @@ public void onSignalingError(String s) { makeSnackbar(logTag, "Signaling Error:" + s, mcMan.getFragmentPub()); } + @Override - public void onStatsReport(RTCStatsReport statsReport) { - String logTag = logTagClass + "[Stat] "; - String log = statsReport.toString(); - logD(TAG, log, logTag); + public void onStatsReport(@NonNull RtsReport rtsReport) { + String logTag = logTagClass + "[Stat]"; + StringBuilder log = new StringBuilder(); + rtsReport.stats().forEach(stat -> { + switch (stat.statsType()){ + case CODEC -> { + log.append("[CODEC] " + ((Codecs)stat).getMimeType()); + } + case INBOUND_RTP -> { + log.append("[INBOUND_RTP] " + "FPS :" + ((InboundRtpStream)stat).getFramesPerSecond()); + } + case OUTBOUND_RTP -> { + log.append("[OUTBOUND_RTP] " + "FPS :"+ ((OutboundRtpStream)stat).getFramesPerSecond()); + } + case VIDEO_SOURCE -> { + log.append("[VIDEO_SOURCE] " + "FPS :"+ ((VideoSource)stat).getFramesPerSecond()); + } + } + + }); + logD(TAG, log.toString(), logTag); } @Override @@ -107,4 +141,30 @@ private void setUI() { }); } + @Override + public void onTransformableFrame(int i, int i1, @NonNull ArrayList arrayList) { + + } + + public void onRecordingStarted(){ + logD(TAG, "[Rec][Start]", "Started recording"); + mcMan.setRecordingEnabled(true); + mcMan.getFragmentPub().setUI(); + } + + public void onRecordingStopped(){ + logD(TAG, "[Rec][Stop]", "Stopped recording"); + mcMan.setRecordingEnabled(false); + mcMan.getFragmentPub().setUI(); + + } + + public void onFailedToStartRecording(){ + logD(TAG, "[Rec][Start]"+"Failed to start recording"); + } + + public void onFailedToStopRecording(){ + logD(TAG, "[Rec][Stop]", "Failed to stop recording"); + + } } diff --git a/app/src/main/java/com/millicast/android_app/PublishFragment.java b/app/src/main/java/com/millicast/android_app/PublishFragment.java index 2b400cb..dd33470 100644 --- a/app/src/main/java/com/millicast/android_app/PublishFragment.java +++ b/app/src/main/java/com/millicast/android_app/PublishFragment.java @@ -23,9 +23,9 @@ import java.util.ArrayList; -import com.millicast.AudioTrack; import com.millicast.VideoRenderer; -import com.millicast.VideoTrack; +import com.millicast.devices.track.AudioTrack; +import com.millicast.devices.track.VideoTrack; import org.webrtc.RendererCommon; @@ -293,17 +293,8 @@ private void toggleAudioOnly(CompoundButton buttonView, boolean isChecked) { } private void toggleRecordingEnabled(View view){ - if(mcMan.isRecordingEnabledPub()){ - if(mcMan.setRecordingEnabled(false)){ - buttonRecording.setText("Recording:F"); - }; - - } - else{ - if(mcMan.setRecordingEnabled(true)) { - buttonRecording.setText("Recording:T"); - } - } + logD(TAG, "[Rec]", "Toggling recording..."); + mcMan.toggleRecordingEnabled(); } @@ -477,6 +468,7 @@ private void onStartPublishClicked(View view) { private void onStopPublishClicked(View view) { Log.d(TAG, "Stop Publish clicked."); + mcMan.stopPub(); setUI(); } @@ -668,6 +660,14 @@ void setUI() { buttonPublish.setEnabled(true); break; } + + if(mcMan.isRecordingEnabledPub()){ + buttonRecording.setText("Recording: ON"); + } + else{ + buttonRecording.setText("Recording: OFF"); + } + setMuteButtons(true); } diff --git a/app/src/main/java/com/millicast/android_app/SettingsMediaFragment.java b/app/src/main/java/com/millicast/android_app/SettingsMediaFragment.java index bcba09f..c797814 100644 --- a/app/src/main/java/com/millicast/android_app/SettingsMediaFragment.java +++ b/app/src/main/java/com/millicast/android_app/SettingsMediaFragment.java @@ -18,9 +18,9 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.millicast.AudioSource; -import com.millicast.VideoCapabilities; -import com.millicast.VideoSource; +import com.millicast.devices.source.audio.AudioSource; +import com.millicast.devices.source.video.VideoCapabilities; +import com.millicast.devices.source.video.VideoSource; import java.util.ArrayList; diff --git a/app/src/main/java/com/millicast/android_app/SourceInfo.java b/app/src/main/java/com/millicast/android_app/SourceInfo.java index 9bbab6c..fe2e204 100644 --- a/app/src/main/java/com/millicast/android_app/SourceInfo.java +++ b/app/src/main/java/com/millicast/android_app/SourceInfo.java @@ -5,8 +5,9 @@ import androidx.annotation.NonNull; -import com.millicast.LayerData; import com.millicast.Subscriber; +import com.millicast.subscribers.ProjectionData; +import com.millicast.subscribers.state.LayerData; import java.util.ArrayList; import java.util.HashMap; @@ -80,14 +81,14 @@ public boolean hasVideo() { } /** - * Get an ArrayList of {@link com.millicast.Subscriber.ProjectionData} for all the + * Get an ArrayList of {@link ProjectionData} for all the * audio or video tracks of this MediaInfo. * * @param mid The mid onto which this source is requested to be projected onto. * @param forAudio * @return */ - public ArrayList getProjectionData(String mid, boolean forAudio) { + public ArrayList getProjectionData(String mid, boolean forAudio) { String[] trackList = trackIdAudioList; String mediaType = "audio"; @@ -96,7 +97,7 @@ public ArrayList getProjectionData(String mid, boolea mediaType = "video"; } - ArrayList result = null; + ArrayList result = null; if (trackList == null || trackList.length < 1) { return result; } @@ -104,11 +105,8 @@ public ArrayList getProjectionData(String mid, boolea result = new ArrayList<>(); for (String trackId : trackList) { - Subscriber.ProjectionData data = new Subscriber.ProjectionData(); - data.trackId = trackId; - data.media = mediaType; - data.mid = mid; - + ProjectionData data = new ProjectionData(trackId,mediaType,mid,null); + //data.copy(trackId,mediaType,mid,null); result.add(data); } @@ -347,19 +345,16 @@ public static String getLayerStr(LayerData ld, boolean longForm) { name += "N.A."; return name; } - - name += ld.encodingId; - String temStr = "" + ld.temporalLayerId; - String spaStr = "" + ld.spatialLayerId; + name += ld.getEncodingId(); + String temStr = "" + ld.getTemporalLayerId(); + String spaStr = "" + ld.getSpatialLayerId(); if (longForm) { String maxTemLayer = "-"; - if (ld.maxTemporalLayerId.isPresent()) { - maxTemLayer = "" + ld.maxTemporalLayerId.get(); - } + maxTemLayer = "" + ld.getMaxTemporalLayerId(); + String maxSpaLayer = "-"; - if (ld.maxSpatialLayerId.isPresent()) { - maxSpaLayer = "" + ld.maxSpatialLayerId.get(); - } + + maxSpaLayer = "" + ld.getMaxSpatialLayerId(); temStr += "/" + maxTemLayer; spaStr += "/" + maxSpaLayer; } diff --git a/app/src/main/java/com/millicast/android_app/SubListener.java b/app/src/main/java/com/millicast/android_app/SubListener.java index 1e20b7f..466548c 100644 --- a/app/src/main/java/com/millicast/android_app/SubListener.java +++ b/app/src/main/java/com/millicast/android_app/SubListener.java @@ -1,13 +1,20 @@ package com.millicast.android_app; -import com.millicast.AudioTrack; -import com.millicast.LayerData; +import com.millicast.clients.stats.Codecs; +import com.millicast.clients.stats.InboundRtpStream; +import com.millicast.clients.stats.OutboundRtpStream; +import com.millicast.clients.stats.RtsReport; +import com.millicast.clients.stats.VideoSource; +import com.millicast.devices.track.AudioTrack; import com.millicast.Subscriber; -import com.millicast.VideoTrack; +import com.millicast.devices.track.VideoTrack; +import com.millicast.subscribers.SubscriberListener; +import com.millicast.subscribers.state.LayerData; import org.webrtc.RTCStatsReport; import java.util.ArrayList; +import java.util.Arrays; import java.util.Optional; import static com.millicast.android_app.MCStates.SubscriberState.CONNECTED; @@ -15,11 +22,13 @@ import static com.millicast.android_app.Utils.logD; import static com.millicast.android_app.Utils.makeSnackbar; +import androidx.annotation.NonNull; + /** * Implementation of Subscriber's Listener. * This handles events sent to the Subscriber being listened to. */ -public class SubListener implements Subscriber.Listener { +public class SubListener implements SubscriberListener { public static final String TAG = "SubListener"; private final MillicastManager mcMan; @@ -48,6 +57,7 @@ public void onConnected() { String logTag = logTagClass + "[Con][On] "; mcMan.setSubState(CONNECTED); makeSnackbar(logTag, "Connected", mcMan.getFragmentSub()); + mcMan.getFragmentSub().setUI(); mcMan.startSub(); } @@ -69,6 +79,11 @@ public void onConnectionError(int status, String reason) { public void onStopped() { String logTag = logTagClass + "[Stop] "; logD(TAG, logTag + "OK."); + logD(TAG, logTag + "Subscribe stopped and we have disconnected."); + mcMan.setSubState(MCStates.SubscriberState.DISCONNECTED); + setUI(); + makeSnackbar(logTag, "Subscriber Stopped", mcMan.getFragmentSub()); + } @Override @@ -77,21 +92,41 @@ public void onSignalingError(String s) { makeSnackbar(logTag, "Signaling Error:" + s, mcMan.getFragmentSub()); } + + @Override - public void onStatsReport(RTCStatsReport statsReport) { + public void onStatsReport(@NonNull RtsReport rtsReport) { String logTag = logTagClass + "[Stat] "; - String log = statsReport.toString(); - logD(TAG, log, logTag); - statsReport.getStatsMap().forEach((key,stat) -> { - if (stat.getType().equals("codec")){ - String[] codec = stat.getMembers().get("mimeType").toString().split("/"); - ArrayList codecs = mcMan.getCodecList(codec[0].equals("audio")); - if (!codecs.contains(codec[1])){ - makeSnackbar(logTag, "Unsupported codec: "+codec[1],mcMan.getFragmentSub()); + StringBuilder log = new StringBuilder(); + rtsReport.stats().forEach(stat -> { + switch (stat.statsType()){ + case CODEC -> { + var codecs = (Codecs)stat; + log.append("[CODEC] " + codecs.getMimeType()); + if(((Codecs) stat).getMimeType().startsWith("video")){ + try{ + String videoCodec = codecs.getMimeType().split("/")[1]; + var supportedCodecs = mcMan.getCodecList(false); + if (!supportedCodecs.contains(videoCodec)){ + makeSnackbar(TAG, "Unsupported Video Codec: "+videoCodec, mcMan.getFragmentSub()); + } + }catch (Exception e){} + } + + } + case INBOUND_RTP -> { + log.append("[INBOUND_RTP] " + "FPS :" + ((InboundRtpStream)stat).getFramesPerSecond()); + } + case OUTBOUND_RTP -> { + log.append("[OUTBOUND_RTP] " + "FPS :"+ ((OutboundRtpStream)stat).getFramesPerSecond()); + } + case VIDEO_SOURCE -> { + log.append("[VIDEO_SOURCE] " + "FPS :"+ ((VideoSource)stat).getFramesPerSecond()); } } - }); + }); + logD(TAG, log.toString(), logTag); } @@ -202,17 +237,18 @@ public void onInactive(String streamId, Optional sourceId) { * @param inactiveLayers inactive simulcast/SVC layers */ @Override - public void onLayers(String mid, LayerData[] activeLayers, LayerData[] inactiveLayers) { + public void onLayers(String mid, LayerData[] activeLayers, String[] inactiveLayers) { String logTag = logTagClass + "[Layer] "; String log = "mid:" + mid + " Active(" + activeLayers.length + "):[" + SourceInfo.getLayerListStr(activeLayers) + "]," + " Inactive(" + inactiveLayers.length + "):[" + - SourceInfo.getLayerListStr(inactiveLayers) + "]."; + Arrays.stream(inactiveLayers).sequential().reduce((a, b) -> a + " " + b) + "]."; logD(TAG, logTag + log); mcMan.setLayerActiveList(activeLayers); } + /** * Called when a source id is being multiplexed into the audio track based on the voice activity level. * @@ -255,4 +291,9 @@ private void setUI() { } }); } + + @Override + public void onFrameMetadata(int i, int i1, @NonNull byte[] bytes) { + + } } diff --git a/app/src/main/java/com/millicast/android_app/SubscribeFragment.java b/app/src/main/java/com/millicast/android_app/SubscribeFragment.java index edb2bde..a5e57e2 100644 --- a/app/src/main/java/com/millicast/android_app/SubscribeFragment.java +++ b/app/src/main/java/com/millicast/android_app/SubscribeFragment.java @@ -18,10 +18,10 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import com.millicast.AudioTrack; -import com.millicast.LayerData; +import com.millicast.devices.track.AudioTrack; import com.millicast.VideoRenderer; -import com.millicast.VideoTrack; +import com.millicast.devices.track.VideoTrack; +import com.millicast.subscribers.state.LayerData; import org.webrtc.RendererCommon; @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; /** * UI for subscribing. @@ -411,46 +412,46 @@ void loadLayerSpinner(ArrayList spinnerList, Utils.GetSelectedIndex lamb populateSpinner(spinnerList, spinner, lambda, mcMan.getContext(), (int position) -> { String logTagSet = "[View][Layer][Spinner][Set] "; - String logSet; + AtomicReference logSet = new AtomicReference<>(); String layerId = spinnerList.get(position); - logSet = "Selected Pos: " + position + " LayerId:" + layerId + "."; + logSet.set("Selected Pos: " + position + " LayerId:" + layerId + "."); logD(TAG, logTagSet + logSet); // Select the layer and if not possible - if (!mcMan.selectLayer(layerId)) { - - // Reset to currently selected layerId if possible. - logSet = "Failed! Resetting to currently selected layerId..."; - logD(TAG, logTagSet + logSet); - - int previousIndex = lambda.getSelectedIndex(spinnerList); - if (previousIndex >= 0) { - String resetSelection = spinnerList.get(previousIndex); - spinner.setSelection(previousIndex); - logSet = "OK. Spinner reset to:" + resetSelection + "."; - logD(TAG, logTagSet + logSet); - return; - } - - // Otherwise reset to automatic selection. - logSet = "Failed! Resetting to automatic selection..."; - logD(TAG, logTagSet + logSet); - - String resetLayerId = ""; - mcMan.selectLayer(""); - int resetIndex = spinnerList.indexOf(resetLayerId); - if (resetIndex > 0) { - spinner.setSelection(resetIndex); - logSet = "OK. Spinner reset to index:" + resetLayerId + "."; - } else { - logSet = "Failed! Unable to find index of resetLayerId(" + resetLayerId + - ") as it is not in the current spinnerList:" + spinnerList + "."; - } - logD(TAG, logTagSet + logSet); - } else { - logSet = "OK."; - logD(TAG, logTagSet + logSet); - } + mcMan.selectLayer(layerId) + .then(success -> { + logD(TAG, logTagSet + "OK"); + }) + .error(e -> { + // Reset to currently selected layerId if possible. + logSet.set("Failed! Resetting to currently selected layerId..."); + logD(TAG, logTagSet + logSet); + + int previousIndex = lambda.getSelectedIndex(spinnerList); + if (previousIndex >= 0) { + String resetSelection = spinnerList.get(previousIndex); + spinner.setSelection(previousIndex); + logSet.set("OK. Spinner reset to:" + resetSelection + "."); + logD(TAG, logTagSet + logSet); + return; + } + + // Otherwise reset to automatic selection. + logSet.set("Failed! Resetting to automatic selection..."); + logD(TAG, logTagSet + logSet); + + String resetLayerId = ""; + mcMan.selectLayer(""); + int resetIndex = spinnerList.indexOf(resetLayerId); + if (resetIndex > 0) { + spinner.setSelection(resetIndex); + logSet.set("OK. Spinner reset to index:" + resetLayerId + "."); + } else { + logSet.set("Failed! Unable to find index of resetLayerId(" + resetLayerId + + ") as it is not in the current spinnerList:" + spinnerList + "."); + } + logD(TAG, logTagSet + logSet); + }); }); } diff --git a/app/src/main/java/com/millicast/android_app/SwitchHdl.java b/app/src/main/java/com/millicast/android_app/SwitchHdl.java index f5f593f..274aeb2 100644 --- a/app/src/main/java/com/millicast/android_app/SwitchHdl.java +++ b/app/src/main/java/com/millicast/android_app/SwitchHdl.java @@ -1,6 +1,7 @@ package com.millicast.android_app; -import com.millicast.VideoSource; +import com.millicast.devices.source.video.VideoSource; +import com.millicast.devices.type.SwitchCameraHandler; import static com.millicast.android_app.Utils.logD; @@ -8,7 +9,7 @@ * Implementation of VideoSource's camera switch listener. * This handles camera switch events that allows us to know the outcome and details of camera switching. */ -class SwitchHdl implements VideoSource.SwitchCameraHandler { +class SwitchHdl implements SwitchCameraHandler { public static final String TAG = "SwitchHdl"; private String logTag = "[Video][Source][Cam][Switch][Hdl] "; diff --git a/app/src/main/java/com/millicast/android_app/VideoSourceEvtHdl.java b/app/src/main/java/com/millicast/android_app/VideoSourceEvtHdl.java index 0491d2d..d918e48 100644 --- a/app/src/main/java/com/millicast/android_app/VideoSourceEvtHdl.java +++ b/app/src/main/java/com/millicast/android_app/VideoSourceEvtHdl.java @@ -2,7 +2,8 @@ import android.os.Handler; -import com.millicast.VideoSource; +import com.millicast.devices.source.video.VideoSource; +import com.millicast.devices.type.EventsHandler; import static com.millicast.android_app.MCStates.*; import static com.millicast.android_app.MCTypes.Source.CURRENT; @@ -13,7 +14,7 @@ * Implementation of VideoSource's event listener. * This handles camera events that allows us to know the outcome and details of camera operations. */ -class VideoSourceEvtHdl implements VideoSource.EventsHandler { +class VideoSourceEvtHdl implements EventsHandler { public static final String TAG = "VidSrcEvtHdl"; private MillicastManager mcMan; diff --git a/app/src/main/java/com/millicast/android_app/compat/CompatBase.kt b/app/src/main/java/com/millicast/android_app/compat/CompatBase.kt new file mode 100644 index 0000000..483bf6d --- /dev/null +++ b/app/src/main/java/com/millicast/android_app/compat/CompatBase.kt @@ -0,0 +1,49 @@ +package com.millicast.android_app.compat + +import android.util.Log +import com.millicast.publishers.BitrateSettings +import com.voxeet.promise.Promise +import com.voxeet.promise.PromiseDebug +import com.voxeet.promise.solve.Solver +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import java.util.logging.Logger + +open abstract class CompatBase() { + val scope = MainScope(); + + fun promise(block: suspend () -> T): Promise { + return Promise { solver -> + run{ + GlobalScope.launch { + try { + solver.resolve(block()); + } catch (err: Throwable) { + solver.reject(err) + } + }; + } + }; + } + + +} +class CompatBitrateSettings{ + var disableBWE: Boolean = false + + var maxBitrateKbps: Int? = null; + + var minBitrateKbps : Int? = null + + + fun get() : BitrateSettings { + return BitrateSettings( + this.disableBWE, + this.maxBitrateKbps, + this.minBitrateKbps + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/millicast/android_app/compat/PublisherCompat.kt b/app/src/main/java/com/millicast/android_app/compat/PublisherCompat.kt new file mode 100644 index 0000000..906d685 --- /dev/null +++ b/app/src/main/java/com/millicast/android_app/compat/PublisherCompat.kt @@ -0,0 +1,145 @@ +package com.millicast.android_app.compat + +import android.util.Log +import com.millicast.Core +import com.millicast.android_app.PubListener +import com.millicast.clients.ConnectionOptions +import com.millicast.clients.state.ConnectionState +import com.millicast.devices.track.AudioTrack +import com.millicast.devices.track.Track +import com.millicast.devices.track.VideoTrack +import com.millicast.publishers.BitrateSettings +import com.millicast.publishers.Option +import com.millicast.publishers.PublisherState +import com.millicast.publishers.state.PublishingState +import com.millicast.publishers.state.RecordingState +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch + + +class PublisherCompat(pubListener: PubListener) : CompatBase() { + + val publisher = Core.createPublisher(); + val listener = pubListener; + var credentials = CompatPublisherCredentials(); + var options = CompatPublisherOptions(); + var state = PublisherState(); + fun connect() = promise { + stateChange(); + val connOpts = ConnectionOptions(false); + publisher.enableStats(true) + publisher.setCredentials(credentials.get()); + onStatsReport() + publisher.connect(connectionOptions = connOpts); + } + fun stateChange() = scope.launch { + publisher.state + .distinctUntilChanged() + .collect { newState: PublisherState -> + if(newState.connectionState != state.connectionState){ + when(newState.connectionState){ + ConnectionState.Connected -> listener.onConnected() + ConnectionState.Connecting -> {} + ConnectionState.Default -> {} + ConnectionState.Disconnected -> listener.onDisconnected() + ConnectionState.Disconnecting -> {} + is ConnectionState.DisconnectedError -> { + val error = newState.connectionState as ConnectionState.DisconnectedError; + listener.onConnectionError(error.httpCode,error.reason); + } + }; + } + + if(newState.publishingState != state.publishingState){ + when(newState.publishingState){ + is PublishingState.Error -> { + val error = newState.publishingState as PublishingState.Error + listener.onPublishingError(error.reason) + } + PublishingState.Started -> listener.onPublishing() + PublishingState.Stopped -> {} + } + } + + + if(newState.recordingState != state.recordingState){ + when(newState.recordingState){ + RecordingState.Started -> listener.onRecordingStarted() + RecordingState.StartedFailed -> listener.onFailedToStartRecording() + RecordingState.Stopped -> listener.onRecordingStopped() + RecordingState.StoppedFailed -> listener.onFailedToStopRecording() + } + } + + if(newState.viewers != state.viewers){ + listener.onViewerCount(newState.viewers); + } + state = newState; + } + } + + fun getStats(interval: Boolean) = promise { publisher.enableStats(interval) } + + fun isConnected() = promise { publisher.isConnected } + + fun isRecording() = state.recordingState == RecordingState.Started; + + fun release() = publisher.release(); + fun addTrack(audioTrackPub: AudioTrack) = promise { publisher.addTrack(audioTrackPub as Track); } + + fun addTrack(videoTrackPub: VideoTrack) = promise { publisher.addTrack(videoTrackPub as Track) } + + fun publish() = promise { + publisher.setCredentials(credentials.get()) + publisher.publish(options.get()); + } + + fun startRecording() = promise { publisher.recording.start() } + + fun stopRecording() = promise { publisher.recording.stop() } + + fun unpublish() = promise { publisher.unpublish() } + + fun isPublishing() = promise { publisher.isPublishing } + + fun onStatsReport() = scope.launch { + publisher.rtcStatsReport.distinctUntilChanged().collect{ report -> listener.onStatsReport(report) } + } +} + +class CompatPublisherOptions(){ + var stereo : Boolean = true; + var recordStream = false; + var sourceId = ""; + var bitrateSettings = CompatBitrateSettings(); + var audioCodec = ""; + var videoCodec = "" + + fun get() : Option { + return Option( + stereo = this.stereo, + recordStream = this.recordStream, + sourceId = null, + bitrateSettings = BitrateSettings( + minBitrateKbps = bitrateSettings.minBitrateKbps, + maxBitrateKbps = bitrateSettings.maxBitrateKbps, + disableBWE = bitrateSettings.disableBWE + ), + videoCodec = this.videoCodec, + audioCodec = this.audioCodec + ) + } +} + +class CompatPublisherCredentials() { + var apiUrl = ""; + var streamName = ""; + var token = ""; + + fun get() = com.millicast.publishers.Credential( + streamName = this.streamName, + token = this.token, + apiUrl = this.apiUrl, + + ); +} \ No newline at end of file diff --git a/app/src/main/java/com/millicast/android_app/compat/SubscriberCompat.kt b/app/src/main/java/com/millicast/android_app/compat/SubscriberCompat.kt new file mode 100644 index 0000000..da684c9 --- /dev/null +++ b/app/src/main/java/com/millicast/android_app/compat/SubscriberCompat.kt @@ -0,0 +1,156 @@ +package com.millicast.android_app.compat + +import android.util.Log +import com.millicast.Core +import com.millicast.clients.state.ConnectionState +import com.millicast.devices.track.AudioTrack +import com.millicast.devices.track.TrackType +import com.millicast.devices.track.VideoTrack +import com.millicast.subscribers.Credential +import com.millicast.subscribers.Option +import com.millicast.subscribers.ProjectionData +import com.millicast.subscribers.SubscriberListener +import com.millicast.subscribers.SubscriberState +import com.millicast.subscribers.state.ActivityStream +import com.millicast.subscribers.state.LayerData +import com.millicast.subscribers.state.SubscriptionState +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import java.util.Optional + + +class SubscriberCompat(subscriberListener: SubscriberListener) : CompatBase() { + val subscriber = Core.createSubscriber(); + val listener = subscriberListener; + var credentials = CompatSubscriberCreds(); + var options = CompatSubscriberOptions(); + var state = SubscriberState(); + fun connect() = promise { + stateChange(); + activityChange() + onTrack(); + onLayers(); + onVad(); + subscriber.setCredentials(credentials.get()); + subscriber.connect() + } + fun stateChange() = scope.launch { + // given a known subscriber object + subscriber.state + .distinctUntilChanged() + .collect { newState: SubscriberState -> + // and then perform any operation that fit our logic + // here just logging those + Log.d("[Kotlin]", "new state received $newState") + if(!newState.connectionState.equals(state.connectionState)){ + when (newState.connectionState){ + ConnectionState.Connected -> listener.onConnected() + ConnectionState.Connecting -> {} + ConnectionState.Default -> {} + ConnectionState.Disconnected -> listener.onDisconnected() + is ConnectionState.DisconnectedError -> { + val error = newState.connectionState as ConnectionState.DisconnectedError; + listener.onConnectionError(error.httpCode,error.reason); + } + ConnectionState.Disconnecting -> {} + } + } + if(newState.subscriptionState != state.subscriptionState){ + when(newState.subscriptionState){ + SubscriptionState.Default -> {} + is SubscriptionState.Error -> { + val error = newState.subscriptionState as SubscriptionState.Error; + listener.onSubscribedError(error.reason); + } + SubscriptionState.Stopped -> listener.onStopped() + SubscriptionState.Subscribed -> listener.onSubscribed() + } + } + + if (newState.viewers != state.viewers){ + listener.onViewerCount(newState.viewers); + } + + state = newState; + } + } + + fun activityChange() = scope.launch { + subscriber.activity.distinctUntilChanged().collect{ + newActivity -> + when(newActivity){ + is ActivityStream.Active -> newActivity.sourceId?.let { Optional.of(it) }?.let { listener.onActive(newActivity.streamId,newActivity.track, it) } + is ActivityStream.Inactive -> newActivity.sourceId?.let { Optional.of(it) }?.let { listener.onInactive(newActivity.streamId,Optional.of(newActivity.sourceId!!)) } + } + } + } + + + private fun onTrack() = scope.launch { + subscriber.track.collect() { holder -> + if(holder.track.kind.equals(TrackType.Audio)) + listener.onTrack(holder.track as AudioTrack, Optional.of(holder.mid.orEmpty())); + if(holder.track.kind.equals(TrackType.Video)) + listener.onTrack(holder.track as VideoTrack, Optional.of(holder.mid.orEmpty())); + } + } + + private fun onLayers() = scope.launch { + subscriber.layers.distinctUntilChanged().collect{ + layer -> + listener.onLayers(layer.mid,layer.activeLayers, layer.inactiveLayersEncodingIds) + } + } + + fun onVad() = scope.launch { + subscriber.vad.distinctUntilChanged().collect{ + vad -> + vad.sourceId?.let { Optional.of(it) }?.let { listener.onVad(vad.mid, it) } + } + } + fun select(layerData: LayerData) = promise { subscriber.select(layerData) } + + fun project(sourceId: String, projectionData: ArrayList) = + promise {subscriber.project(sourceId,projectionData)} + + fun addRemoteTrack(trackType: TrackType) = promise{ subscriber.addRemoteTrack(trackType) } + + fun getMid(trackId: String) = promise { subscriber.getMid(trackId) } + + fun getStats(interval: Boolean) = promise { subscriber.enableStats(interval) } + + fun isConnected() = promise { subscriber.isConnected } + + fun release() = subscriber.release(); + + fun subscribe() = promise {subscriber.subscribe(options.get())} + + fun unsubscribe() = promise { subscriber.unsubscribe() } + + fun isSubscribed() = subscriber.isSubscribed; +} + +class CompatSubscriberOptions(){ + var stereo : Boolean = true; + fun get() : Option { + return Option( + stereo = this.stereo + ) + } +} + + +class CompatSubscriberCreds() { + var apiUrl = ""; + var accountId = "" + var streamName = ""; + var token = ""; + + fun get() = Credential( + streamName=this.streamName, + accountId = this.accountId, + token = if (this.token.equals("")) null else this.token, + apiUrl = this.apiUrl + ); +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7778248..a583d68 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + kotlin_version = '2.0.0-Beta3' + } repositories { google() jcenter() + mavenLocal() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:8.2.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -16,6 +21,7 @@ allprojects { repositories { google() jcenter() + mavenLocal() } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 03e8d5f..967e667 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip diff --git a/settings.gradle b/settings.gradle index da45cd8..b3e5407 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,23 @@ +pluginManagement { + buildscript { + repositories { + mavenCentral() + maven { + // r8 maven + url = uri("https://storage.googleapis.com/r8-releases/raw") + } + } + dependencies { + // r8 version + classpath("com.android.tools:r8:8.2.16-dev") + } + } +} + + + include ':MillicastSDK' include ':AndroidSDK' include ':app' -rootProject.name = "android-app" \ No newline at end of file +rootProject.name = "android-app" +