Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ dokka {
prefix = "com.magnitudestudios.GameFace.databinding"
suppress = true
}
packageOptions {
prefix = "com.magnitudestudios.GameFace.ui.camera.CameraFragmentArgs"
suppress = true
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
private lateinit var peerConnectionFactory: PeerConnectionFactory
private var videoCapturer: VideoCapturer? = null

//Local stream setup variables
private lateinit var videoSource: VideoSource
private lateinit var audioSource: AudioSource

Expand All @@ -79,12 +80,15 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
private lateinit var mainViewModel: MainViewModel
private val viewModel: CameraViewModel by navGraphViewModels(R.id.videoCallGraph)

//Maps the Peer UIDs to Video screens
private var videoViews : ConcurrentHashMap<String, MovableScreen> = ConcurrentHashMap()

private lateinit var membersAdapter : MemberStatusAdapter

private val args: CameraFragmentArgs by navArgs()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
//Initialize the peer connection factory
val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(requireContext().applicationContext).createInitializationOptions()
PeerConnectionFactory.initialize(initializationOptions)
bind = FragmentCameraBinding.inflate(inflater)
Expand Down Expand Up @@ -112,6 +116,7 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
observeNewPeers()
observeMembers()

//Set up the listeners for buttons and root surface
bind.root.setOnClickListener {
lifecycleScope.launch {
bind.callingControls.animate().setDuration(5000).alpha(1.0f)
Expand Down Expand Up @@ -162,6 +167,11 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
})
}

/**
* Observe members: updates the users UI when a member has been added/ their status
* has changed.
*
*/
private fun observeMembers() {
membersAdapter = MemberStatusAdapter(viewModel.members.value!!)
bind.showMembers.adapter = membersAdapter
Expand All @@ -183,6 +193,13 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
})
}

/**
* Observe ICE connection
* Called when the user's potential ICE servers from the API
* have been received, and is ready to join a room,
* or create a room
*
*/
private fun observeIceConnection() {
viewModel.iceServers.observe(viewLifecycleOwner, {
if (it != null) {
Expand All @@ -196,6 +213,12 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
})
}

/**
* Observe new peers: Called whenever a new peer has joined.
* Creates the peer connection, and only send the offer if their UID is lexicographically
* greater than the other peer's.
*
*/
private fun observeNewPeers() {
viewModel.newPeer.observe(viewLifecycleOwner, {
if (!it.isNullOrEmpty() && it != Firebase.auth.currentUser!!.uid) {
Expand All @@ -205,6 +228,11 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
})
}

/**
* Start the camera: sets up the camera and audio constraints and
* stream. The local video track is the local stream from your camera
*
*/
private fun startCamera() {
Log.e(TAG, "startCamera: " + "STARTING CAMERA")
// //Create a new PeerConnectionFactory instance - using Hardware encoder and decoder.
Expand Down Expand Up @@ -249,7 +277,13 @@ class CameraFragment : BaseFragment(), View.OnClickListener {

}

/**
* Creates a peer connection with the specified UID
*
* @param uid The UID of the peer
*/
private fun createPeerConnection(uid: String) {
//Configure the connection
val rtcConfig = PeerConnection.RTCConfiguration(viewModel.iceServers.value).apply {
tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
Expand All @@ -258,6 +292,7 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
keyType = PeerConnection.KeyType.ECDSA
}

//Create the peer
val peer = peerConnectionFactory.createPeerConnection(rtcConfig, object : CustomPeerConnectionObserver(uid, "localPeerCreation") {
override fun onIceCandidate(iceCandidate: IceCandidate) {
super.onIceCandidate(iceCandidate)
Expand All @@ -278,13 +313,16 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
})
peer?.let {
it.addStream(localStream)
viewModel.addPeer(uid, it)
viewModel.addPeer(uid, it) //Add the peer to the viewModel
}


}

//For UI updates for each participant
/**
* Update connection status UI
*
* @param uid The uid of the member
* @param iceConnectionState The new ICE connection state
*/
private fun updateConnectionStatus(uid: String, iceConnectionState: PeerConnection.IceConnectionState) {
when (iceConnectionState) {
PeerConnection.IceConnectionState.NEW -> {
Expand All @@ -311,6 +349,11 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
}
}

/**
* Helper function to check whether there are other members in the call still
*
* @return
*/
private fun stillConnectedMembers() : Boolean {
viewModel.connections.value?.forEach {
if (it.value.iceConnectionState() == PeerConnection.IceConnectionState.COMPLETED || it.value.iceConnectionState() == PeerConnection.IceConnectionState.CONNECTED) {
Expand All @@ -320,6 +363,11 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
return false
}

/**
* Removes a peer (UI)
*
* @param uid
*/
@Synchronized
private fun removePeer(uid: String) {
videoViews[uid]?.surface?.release()
Expand All @@ -332,6 +380,12 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
}


/**
* Got a peer stream from the specified UID and is ready to displayed on a screen
*
* @param peerUID The UID of the peer
* @param stream The MediaStream of the peer to attach to screen surface
*/
private fun gotPeerStream(peerUID: String, stream: MediaStream ) {
Log.e(TAG, "gotRemoteStream: " + "GOT REMOTE STREAM")
//we have remote video stream. add to the renderer.
Expand All @@ -347,6 +401,12 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
}
}

/**
* Helper function to retrieve/create a screen for peer given their UID
*
* @param peerUID The UID of the peer
* @return A movable screen object
*/
private fun getScreen(peerUID: String) : MovableScreen {
val videoView : MovableScreen
if (!videoViews.containsKey(peerUID)) {
Expand All @@ -364,25 +424,48 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
return videoView
}


/**
* Transitions the local screen into connected mode
*
*/
private fun transitionConnected() {
bind.localVideo.setCalling()
}

/**
* Transition the local screen into disconnected mode
*
*/
private fun transitionDisconnected() {
bind.localVideo.setLocal()
}

/**
* Sets the screen into loading mode (ie when connecting)
*
* @param b - True when loading, false otherwise.
*/
private fun setLoading(b: Boolean) {
bind.localVideo.setLoading(b)

}

/**
* Connection failed: called when there is an error connecting
*
* @param message
*/
private fun connectionFailed(message: String? = null) {
activity?.runOnUiThread {
if (!message.isNullOrEmpty()) Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
}

/**
* Disconnect the current user from all screens
*
* @param userDefined
*/
private fun disconnect(userDefined: Boolean = false) {
viewModel.hangUp()
transitionDisconnected()
Expand All @@ -407,6 +490,12 @@ class CameraFragment : BaseFragment(), View.OnClickListener {
disconnect()
}

/**
* Finds the camera devices available for the video stream
*
* @param enumerator
* @return
*/
private fun createCameraCapturer(enumerator: CameraEnumerator): VideoCapturer? {
val deviceNames = enumerator.deviceNames
// Trying to find a front facing camera!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,28 @@ import java.util.concurrent.ConcurrentHashMap
* @param application
*/
class CameraViewModel(application: Application) : AndroidViewModel(application), RoomCallback, MemberCallback {
//The name of the connected room
private val connectedRoom = MutableLiveData<String>()

//The list of all the members in the room right now.
val members = MutableLiveData<MutableList<Member>>(mutableListOf())

//New member
val newMember = MutableLiveData<Member>()

//Member changed
val changedMember = MutableLiveData<Int>()

//Maps user UIDs to a peer connection. Thread safe.
val connections = MutableLiveData<ConcurrentHashMap<String, PeerConnection>>(ConcurrentHashMap())

//Keeps track of the current user's connection status
val connectionStatus = MutableLiveData<Resource<Boolean>>(Resource.loading(false))

// The UID of the new peer
val newPeer = MutableLiveData<String>()

//Retrieve the ICE servers from the API
val iceServers = liveData(Dispatchers.IO) {
val data = HTTPRequest.getServers()
if (data.status == Status.ERROR) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.magnitudestudios.GameFace.views.holders.MemberViewHolder

/**
* Member status adapter
* @see SortedRVAdapter
*
* @property members
* @constructor Create empty Member status adapter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ import android.util.Log
import android.view.SurfaceHolder
import org.webrtc.SurfaceViewRenderer

/**
* Custom surface view
*
* @constructor Create empty Custom surface view
*/
class CustomSurfaceView : SurfaceViewRenderer {

constructor(context: Context?) : super(context) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ class MovableScreen @JvmOverloads constructor(
}

/**
* Set calling
* Sets the screen into calling mode
*
*/// Set this surface view as a smaller screen
fun setCalling() {
Expand All @@ -228,7 +228,7 @@ class MovableScreen @JvmOverloads constructor(
}

/**
* Sets the screen into local
* Sets the screen into local mode
*
*/// Sets this as a fullscreen view
fun setLocal() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ import androidx.recyclerview.widget.ItemTouchHelper
import kotlin.math.max

/**
* Video layout
* Video layout: this is the Layout for the Video screens.
* Whenever a screen is added dynamically, this view supports
* resizes and puts all the other screens in the correct places
*
* Currently supports up to six screens
*
* @constructor
*
Expand Down
5 changes: 3 additions & 2 deletions docs/app/alltypes/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ <h3>All Types</h3>
<td>
<a href="../com.magnitudestudios.-game-face.views/-custom-surface-view/index.html">com.magnitudestudios.GameFace.views.CustomSurfaceView</a></td>
<td>
<p>Custom surface view</p>
</td>
</tr>
<tr>
Expand Down Expand Up @@ -644,7 +643,9 @@ <h3>All Types</h3>
<td>
<a href="../com.magnitudestudios.-game-face.views/-video-layout/index.html">com.magnitudestudios.GameFace.views.VideoLayout</a></td>
<td>
<p>Video layout</p>
<p>Video layout: this is the Layout for the Video screens.
Whenever a screen is added dynamically, this view supports
resizes and puts all the other screens in the correct places</p>
</td>
</tr>
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
<h1>connectionFailed</h1>
<a name="com.magnitudestudios.GameFace.ui.camera.CameraFragment$connectionFailed(kotlin.String)"></a>
<code><span class="keyword">private</span> <span class="keyword">fun </span><span class="identifier">connectionFailed</span><span class="symbol">(</span><span class="identifier" id="com.magnitudestudios.GameFace.ui.camera.CameraFragment$connectionFailed(kotlin.String)/message">message</span><span class="symbol">:</span>&nbsp;<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a><span class="symbol">?</span>&nbsp;<span class="symbol">=</span>&nbsp;null<span class="symbol">)</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html"><span class="identifier">Unit</span></a></code>
<p>Connection failed: called when there is an error connecting</p>
<h3>Parameters</h3>
<p><a name="message"></a>
<code>message</code> - </p>
</BODY>
</HTML>
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,11 @@
<h1>createCameraCapturer</h1>
<a name="com.magnitudestudios.GameFace.ui.camera.CameraFragment$createCameraCapturer(org.webrtc.CameraEnumerator)"></a>
<code><span class="keyword">private</span> <span class="keyword">fun </span><span class="identifier">createCameraCapturer</span><span class="symbol">(</span><span class="identifier" id="com.magnitudestudios.GameFace.ui.camera.CameraFragment$createCameraCapturer(org.webrtc.CameraEnumerator)/enumerator">enumerator</span><span class="symbol">:</span>&nbsp;<span class="identifier">CameraEnumerator</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">VideoCapturer</span><span class="symbol">?</span></code>
<p>Finds the camera devices available for the video stream</p>
<h3>Parameters</h3>
<p><a name="enumerator"></a>
<code>enumerator</code> - </p>
<p><strong>Return</strong><br/>
</p>
</BODY>
</HTML>
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
<h1>createPeerConnection</h1>
<a name="com.magnitudestudios.GameFace.ui.camera.CameraFragment$createPeerConnection(kotlin.String)"></a>
<code><span class="keyword">private</span> <span class="keyword">fun </span><span class="identifier">createPeerConnection</span><span class="symbol">(</span><span class="identifier" id="com.magnitudestudios.GameFace.ui.camera.CameraFragment$createPeerConnection(kotlin.String)/uid">uid</span><span class="symbol">:</span>&nbsp;<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a><span class="symbol">)</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html"><span class="identifier">Unit</span></a></code>
<p>Creates a peer connection with the specified UID</p>
<h3>Parameters</h3>
<p><a name="uid"></a>
<code>uid</code> - The UID of the peer</p>
</BODY>
</HTML>
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
<h1>disconnect</h1>
<a name="com.magnitudestudios.GameFace.ui.camera.CameraFragment$disconnect(kotlin.Boolean)"></a>
<code><span class="keyword">private</span> <span class="keyword">fun </span><span class="identifier">disconnect</span><span class="symbol">(</span><span class="identifier" id="com.magnitudestudios.GameFace.ui.camera.CameraFragment$disconnect(kotlin.Boolean)/userDefined">userDefined</span><span class="symbol">:</span>&nbsp;<a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-boolean/index.html"><span class="identifier">Boolean</span></a>&nbsp;<span class="symbol">=</span>&nbsp;false<span class="symbol">)</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-unit/index.html"><span class="identifier">Unit</span></a></code>
<p>Disconnect the current user from all screens</p>
<h3>Parameters</h3>
<p><a name="userDefined"></a>
<code>userDefined</code> - </p>
</BODY>
</HTML>
Loading