in app/src/main/java/com/amazonaws/ivs/basicbroadcast/viewModel/MixerViewModel.kt [49:175]
fun createSession(logo: Bitmap, content: Uri) {
session?.release()
// Create a custom configuration at 720p60.
val config = BroadcastConfiguration().apply {
this.video.size = bigSize
this.video.targetFramerate = 60
// This slot will hold the camera and start in the bottom left corner of the stream. It will move during the transition.
cameraSlot = BroadcastConfiguration.Mixer.Slot.with {
it.size = smallSize
it.aspect = BroadcastConfiguration.AspectMode.FIT
it.position = smallPositionBottomLeft
it.setzIndex(2)
it.preferredVideoInput = Device.Descriptor.DeviceType.CAMERA
it.name = CAMERA_SLOT_NAME
return@with it
}
// This slot will hold custom content (in this example, a looping mp4 file) and take up the entire stream. It will move during the transition.
contentSlot = BroadcastConfiguration.Mixer.Slot.with {
it.size = bigSize
it.position = bigPosition
it.setzIndex(1)
it.name = CONTENT_SLOT_NAME
return@with it
}
// This slot will be a logo-based watermark and sit in the bottom right corner of the stream. It will not move around.
logoSlot = BroadcastConfiguration.Mixer.Slot.with {
it.size = BroadcastConfiguration.Vec2(smallSize.y, smallSize.y) // 1:1 aspect
it.position = BroadcastConfiguration.Vec2(bigSize.x - smallSize.y - borderWidth, smallPositionBottomRight.y)
it.setzIndex(3)
it.transparency = 0.3f
it.name = LOGO_SLOT_NAME
return@with it
}
this.mixer.slots = arrayOf(cameraSlot, contentSlot, logoSlot)
}
BroadcastSession(context, null, config, null).apply {
session = this
Log.d(TAG, "Broadcast session ready: $isReady")
if (!isReady) {
Log.d(TAG, "Broadcast session not ready")
Toast.makeText(context, context.getString(R.string.error_create_session), Toast.LENGTH_SHORT).show()
return
}
// Attach devices to each slot manually based on the slot names.
// Find the first front camera.
val frontCamera = BroadcastSession.listAvailableDevices(context).filter {
it.position == Device.Descriptor.Position.FRONT && it.type == Device.Descriptor.DeviceType.CAMERA
}[0]
frontCamera?.let {
// Then, we attach the front camera and on completion, bind it to the camera slot.
// Note that bindToPreference is FALSE, which gives us full control over binding the device to the slot. This also means
// that we are responsible for binding the device to a slot once the device is attached.
// (When bindToPreference is TRUE, as part of attaching the device, the broadcast session will also try to bind the device to a
// slot with a matching type preference.)
this.attachDevice(frontCamera, false) {
val success: Boolean = this.mixer?.bind(it, CAMERA_SLOT_NAME) == true
// Error-checking. The most common source of this error is that there is no slot
// with the name provided.
if (!success) {
Toast.makeText(context, context.getString(R.string.error_failed_to_bind_to_slot), Toast.LENGTH_SHORT).show()
}
}
}
// Second, create a custom image input source for the logo.
val logoSurfaceSource = this.createImageInputSource()
val logoSurface = logoSurfaceSource.inputSurface
val canvas = logoSurface.lockCanvas(null)
canvas.drawBitmap(logo, 0f, 0f, null)
logoSurface.unlockCanvasAndPost(canvas)
// Bind it to the logo slot.
this.awaitDeviceChanges {
val success: Boolean = this.mixer?.bind(logoSurfaceSource, LOGO_SLOT_NAME) == true
// Error-checking. The most common source of this error is that there is no slot
// with the name provided.
if (!success) {
Toast.makeText(context, context.getString(R.string.error_failed_to_bind_to_slot), Toast.LENGTH_SHORT).show()
}
}
// Third, create a custom image input source for the looping content.
val contentSurfaceSource = this.createImageInputSource()
val contentSurface = contentSurfaceSource.inputSurface
player = MediaPlayer().apply {
this.setDataSource(context, content)
this.prepare()
this.setDisplay(CustomSurfaceHolder(contentSurface))
this.setOnPreparedListener {
this.start()
this.isLooping = true
}
}
// Bind it to the content slot.
this.awaitDeviceChanges {
val success: Boolean = this.mixer?.bind(contentSurfaceSource, CONTENT_SLOT_NAME) == true
// Error-checking. The most common source of this error is that there is no slot
// with the name provided.
if (!success) {
Toast.makeText(context, context.getString(R.string.error_failed_to_bind_to_slot), Toast.LENGTH_SHORT).show()
}
}
// This creates a preview of the composited output stream, not an individual source. Because of this there is small
// amount of delay in the preview since it has to go through a render cycle to composite the sources together.
// It is also important to note that because our configuration is for a landscape stream using the "fit" aspect mode
// there will be aggressive letterboxing when holding a mobile phone in portrait. Rotating to landscape or using an tablet
// will provide a larger preview, though the only change is the scaling.
this.awaitDeviceChanges {
displayPreview()
}
}
}