
Compartir:
Arjuna Sky Kok is the author of "Hands-on Blockchain for Python Developers" and the creator of PredictSalary, a tool to predict the salary ranges from job opportunities. He lives in Jakarta, Indonesia
Crear historias al estilo de las redes sociales con Android y Python
Tiempo de lectura: 20 minutos
Instagram tiene una función muy popular, que se llama Stories, muy inspirada en Snapchat. Stories permite a los usuarios crear un Video o una foto fija que desaparecerá en un breve periodo de tiempo (24 horas). Más tarde, otras plataformas de medios sociales desarrollaron también esta función, como Linkedin y Twitter.
En este tutorial, construiremos esta función para Android con la Video API de Vonage, también necesitaremos un servidor para manejar las sesiones y tokens para el cliente, el cual será construido usando Python.
Requisitos previos
Dispositivo Android (opcional)
Ngrok (opcional)
Construcción del servidor
Para empezar, vamos a construir un servidor con el que nuestra aplicación móvil pueda comunicarse. Este servidor utilizará Djangoun framework web de Python.
Instalación de dependencias
En primer lugar, cree un entorno virtual Python ejecutando los dos comandos siguientes:
A continuación, podemos instalar las dependencias para nuestro servidor backend:
El comando anterior instalará Django y Opentok el SDK de Python para Video de Vonage.
Crear un proyecto y una aplicación
Inicializa tu proyecto Django ejecutando el siguiente comando:
Antes de lanzarnos a escribir el código, necesitamos crear al menos una aplicación dentro del proyecto Django. Un proyecto Django puede consistir en muchas aplicaciones. Por ejemplo, el proyecto A puede ser una aplicación de Historias, mientras que el proyecto B podría ser una aplicación de mensajería o una aplicación de comercio electrónico. Primero, cambia tu directorio actual al directorio del proyecto y luego inicializa una nueva aplicación con los dos comandos que se muestran a continuación:
No es necesario ejecutar el siguiente comando porque no utilizaremos una base de datos en este tutorial. Sin embargo, una advertencia persistirá a menos que ejecutemos el comando de migración, así que ejecute lo siguiente para detener estas advertencias.
Para asegurarnos de que nuestra aplicación web Django funciona, podemos ejecutar el script del servidor web.
Puede acceder a su aplicación web en localhost con el puerto 8000. Que, en su navegador se verá como http://localhost:8000. Si vas a esta URL, serás recibido con un mensaje de éxito y una imagen de un cohete.
Variables de entorno
Nuestro código necesitará usar tu clave y secreto de la API de Vonage Video (también conocido como OpenTok), que nunca deben estar codificados en tu aplicación. Para aumentar la seguridad de la aplicación, puedes utilizar variables de entorno. En tu Terminal, puedes establecer tus dos valores como se muestra en los ejemplos a continuación. Asegúrese de reemplazar xxxxxx por sus valores. Puedes obtener estos valores para la clave y el secreto de la API en los Documentación de la Video API de Vonage.
Crear las vistas
Las vistas son las plantillas que se muestran en tu aplicación Django. Abre el archivo storiesserver/storiesapp/views.py y sustituye su contenido por el siguiente:
from django.http import HttpResponse, JsonResponse
import os, time
from opentok import OpenTok, MediaModes, ArchiveModes
api_key = os.environ["OPENTOK_API_KEY"]
api_secret = os.environ["OPENTOK_API_SECRET"]
opentok = OpenTok(api_key, api_secret)
videos = [
]
index = 1
def get_token(request):
global opentok
session = opentok.create_session(media_mode=MediaModes.routed, archive_mode=ArchiveModes.manual)
token = session.generate_token(expire_time=int(time.time()) + 200)
return JsonResponse({"token": token, "session": session.session_id,
"api_key": api_key})
def video_stream(request, archive_id):
global opentok
video = opentok.get_archive(archive_id)
return HttpResponse(video.url)
def videos_list(request):
global videos
return JsonResponse(videos, safe=False)
def video_start_archive(request, session_id):
global index, videos
name = f"Story {index}"
archive = opentok.start_archive(session_id)
index += 1
videos.append({
"name": name,
"archive_id": archive.id
})
return HttpResponse(f"{archive.id}")
def video_stop_archive(request, archive_id):
global opentok
opentok.stop_archive(archive_id)
return HttpResponse("Stop Archiving")
def homepage(request):
return HttpResponse("Hello")La primera parte de este código crea una instancia de OpenTok, que utilizará nuestras variables de entorno preestablecidas para la clave y el secreto de la API.
Entonces tenemos dos variables globales videos y index. Son como una mini base de datos en memoria que contiene la información de nuestras historias (o videos).
En el primer método, generamos nuestra sesión y token con el método get_token método. Para crear una sesión, usamos el método create_session de la instancia OpenTok. Acepta dos parámetros: un modo de medios y un modo de archivo. Usamos el valor MediaModes.routed valor para el modo multimedia porque publicamos un Video como una Historia, no para videochat. MediaModes.routed significa que enviamos el vídeo al servidor en lugar de a otro cliente. Utilizamos el valor ArchiveModes.manual para el modo de archivo porque queremos archivar (grabar) el vídeo manualmente para que podamos obtener el id de archivo. Este id es importante cuando queremos obtener el video grabado. Si no, tenemos que iterar desde la lista de archivos, lo cual es inconveniente. Para generar el token, utilizamos el método generate_token de la instancia de sesión. Pasamos el valor del tiempo expirado como parámetro dentro de este método. Luego, al final, enviamos la sesión, el token y la clave API al cliente.
En el segundo método, el método video_stream obtenemos la URL del vídeo a partir del identificador del archivo. Si te fijas, este método tiene un parámetro adicional además del parámetro habitual, request. Cómo obtenemos el id del archivo se hace en otro método.
En el tercer método, el videos_list enviamos la lista de nuestros vídeos al cliente. Es básicamente nuestra videos variable envuelta en JSON.
En el cuarto método, el video_start_archive archivamos el vídeo. Este método tiene un parámetro adicional, session_id. Utilizamos el método start_archive de la instancia OpenTok. Acepta una sesión de OpenTok. Después de ejecutar este método, nuestra sesión de Video será grabada. El método start_archive devuelve el id del archivo. Lo almacenamos en la variable videos variable. Aparte del id del archivo, generamos un nombre bonito para este Video que es algo como "Historia 1", "Historia 2", etc.
En el quinto método, el video_stop_archive dejamos de archivar el vídeo. Este método tiene un parámetro adicional, archive_id. En otras palabras, dejamos de grabar el vídeo. Utilizamos el método stop_archive de la instancia OpenTok. Acepta el id del archivo como parámetro.
El último método, el método homepage es sólo para comprobar si se puede acceder a la aplicación web Django o no.
Crear mapeo de URL
Luego necesitamos crear un mapeo de URLs a estos métodos de vistas. Crea el archivo storiesserver/storiesapp/urls.py y copia el siguiente ejemplo en el archivo:
from django.urls import path
from . import views
urlpatterns = [
path('', views.homepage, name='index'),
path('token', views.get_token, name='token'),
path('videos/<str:archive_id>', views.video_stream, name='videostream'),
path('videos-list', views.videos_list, name='videoslist'),
path('video-start-archive/<str:session_id>', views.video_start_archive, name='startarchive'),
path('video-stop-archive/<str:archive_id>', views.video_stop_archive, name='stoparchive'),
]El método path acepta tres argumentos:
Ruta URL El método al que se accede Un nombre para la ruta a efectos de referencia.
Por ejemplo, la segunda ruta asigna la token URL al método get_token en las vistas.
En el lado del servidor de la aplicación Django, tenemos que modificar el archivo urls.py para mapear a las URLs de las historias, así que localiza el archivo storiesserver/storiesserver/urls.py y sustituye el contenido de este archivo por:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('stories/', include('storiesapp.urls')),
]En este archivo, asignamos el archivo URL de la aplicación al archivo stories/ URL. Por ejemplo, con la token URL, tendríamos la siguiente URL completa: http://localhost:8000/stories/token.
Ngrok
Para exponer la aplicación Django a Internet y a nuestros dispositivos Android durante el desarrollo, utilizaremos Ngrok. Después de conectar su cuenta, podemos iniciar un túnel HTTP que reenvía al puerto de nuestra aplicación web Django, 8000:
Cuando se haya creado el túnel, verás la URL pública que tiene un aspecto parecido a: https://xxxxxxxx.ngrok.io. A continuación, debe modificar el archivo storiesserver/storiesserver/settings.py añadiendo los caracteres "xxxxxxxx.ngrok.io", "10.0.2.2" y "localhost" a la variable ALLOWED_HOSTS variable. La dirección 10.0.2.2 es la forma en que el emulador de Android accede a localhost en su ordenador.
Su variable ALLOWED_HOSTS debería tener este aspecto después de la modificación:
ALLOWED_HOSTS = ["10.0.2.2", "localhost", "xxxxxxxx.ngrok.io"]Entonces, para acceder a la token URL, accederías a esta URL: https://xxxxxxxx.ngrok.io/stories/token.
En este punto, has construido el componente servidor que puede generar tokens, archivar la grabación de vídeo y entregar la URL de grabación de vídeo. A continuación, vas a construir una aplicación Android que actúe como cliente de esta aplicación servidor. Con esta aplicación móvil, puedes grabar vídeos y ver las grabaciones de vídeo.
Cliente
Abre Android Studio, crea un nuevo proyecto y selecciona "Actividad vacía" como plantilla de proyecto. Para el SDK mínimo, elige "API 16: Android 4.1 (Jelly Bean)" y para el lenguaje, elige "Kotlin".
Dependencias
Lo primero que se necesita cuando se construye un proyecto en Android Studio es añadir las dependencias necesarias. Abra el archivo build.gradle en el nivel de proyecto y añada la siguiente línea dentro del bloque repositories que se encuentra dentro del bloque allprojects bloque. A continuación, sincronice el archivo.
maven { url 'https://tokbox.bintray.com/maven' }La segunda cosa que tienes que hacer es añadir dependencias en el archivo, que tiene el mismo nombre build.gradle pero a nivel de aplicación (o de módulo). Añade estas líneas dentro del bloque dependencies bloque. No olvide sincronizar el archivo.
implementation 'com.opentok.android:opentok-android-sdk:2.19.+'
implementation "com.squareup.okhttp3:okhttp:4.2.1"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'pub.devrel:easypermissions:3.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'Este proyecto utiliza cinco bibliotecas adicionales, que son:
Vonage Video SDK (OpenTok) OkHttp biblioteca para enviar solicitudes a nuestro servidor backend, Gson biblioteca para analizar la respuesta JSON, EasyPermissions biblioteca para manejar los permisos cuando nuestra aplicación Android pide permisos para utilizar la cámara y el micrófono, Coroutines biblioteca para manejar no-bloqueo y bloqueo de código convenientemente.
Configuración de seguridad de red
Este paso es opcional. Si quieres probar la aplicación con un emulador y no quieres usar Ngrok, entonces necesitas crear un archivo de configuración de red. Cree el directorio xml dentro del directorio res y cree un nuevo archivo llamado network_security_config.xml dentro del directorio xml dentro del directorio. Copie lo siguiente en él:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
Esto significa que puede desarrollar la aplicación sin el protocolo HTTPS para la URL del backend.
Manifiesto Android
Para activar esta configuración de red, abra el archivo AndroidManifest.xml y añada el siguiente atributo en application nodo:
android:networkSecurityConfig="@xml/network_security_config"A continuación, añada lo siguiente dentro del nodo manifest nodo.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />Necesitamos estas dos adiciones porque queremos conectar nuestra aplicación móvil a nuestro servidor backend.
Mientras estamos en este archivo, vamos a crear referencias a dos actividades adicionales que serán creadas más tarde. Añade estas líneas después del único nodo activity nodo.
<activity android:name=".ViewingStoryActivity">
</activity>
<activity android:name=".CreatingStoryActivity">
</activity> Diseño
Tenemos tres Actividades, pero necesitamos crear 3 layouts para estas Actividades. También necesitamos crear un diseño adicional para el diseño de filas en un RecyclerView utilizado dentro de la MainActivity.
En primer lugar, cree el diseño de filas creando un nuevo archivo llamado row.xml dentro del directorio app/src/main/res/layout directorio. Copie el siguiente XML en este nuevo archivo::
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:text="TextView"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_margin="16dp"
android:id="@+id/buttonView"
android:layout_weight="1"/>
</LinearLayout>Lo anterior define la creación de un botón, que se utilizará para lanzar una Actividad para ver la historia o el Video.
A continuación, tenemos que crear un diseño para mostrar un Video. Cree un nuevo archivo llamado activity_viewing_story.xml en el directorio del layout, y añade el siguiente XML al archivo:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>El diseño contiene un WebView que reproducirá el vídeo.
A continuación, tenemos que crear el archivo activity_creating_story.xml archivo. Este es el diseño utilizado por la Actividad para crear un Video. Borre el contenido del archivo y añádale el siguiente código:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="MissingConstraints"
>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="120dp"
android:id="@+id/publisher"
/>
<Button
android:layout_margin="48dp"
android:layout_gravity="bottom|end"
android:id="@+id/publishbutton"
android:text="Upload Story"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
tools:ignore="MissingConstraints" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>El FrameLayout con el publisher es utilizado por el SDK de Video para mostrar el video que está siendo enviado al servidor de Video. El propósito del botón en esta Actividad es detener la grabación del video y finalizar la Actividad.
Por último, tenemos que editar el archivo activity_main.xml archivo. Este archivo contiene el diseño utilizado por la MainActivity, que listará todas las historias (videos) que han sido creadas. El archivo también contendrá un botón para lanzar la Actividad para crear un Video. Reemplace el contenido de este archivo con el siguiente XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="89dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="Plus"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="48dp"
android:layout_marginEnd="48dp"
android:layout_marginRight="48dp"
/>
</androidx.constraintlayout.widget.ConstraintLayout> Añadir URL del servidor
Definamos la URL del servidor en el archivo strings.xml que se encuentra en el directorio values directorio. Para ello, añada lo siguiente a su resources nodo:
<string name="SERVER">http://localhost:8000</string>Sin embargo, si está utilizando Ngrok, cámbielo por el siguiente ejemplo (asegúrese de sustituir xxxxx con su URL ngrok):
<string name="SERVER">https://xxxxxx.ngrok.io</string> Clases y actividades
En primer lugar, tendremos que crear las clases necesarias para un RecyclerView. Un RecyclerView necesita un adaptador y un holder. Crea un archivo holder creando el archivo con el nombre StoryViewHolder.kt dentro del directorio package directorio. El paquete será algo similar a com.example.storiesapplication que se encuentra dentro del directorio java directorio. Copie el siguiente código en este nuevo archivo:
package com.example.storiesapplication
import android.view.View
import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
class StoryViewHolder(private val view : View, onClick: (view: View) -> Unit) : RecyclerView.ViewHolder(view) {
private val buttonView : Button = this.view.findViewById(R.id.buttonView)
init {
buttonView.setOnClickListener(onClick)
}
fun bindModel(item : String) {
this.buttonView.text = item
}
}
Para cada clase o archivo Activity que crees, asegúrate de cambiar el com.example.storiesapplication a su paquete si éste es diferente.
Esta es una clase de soporte estándar donde se establece el texto del botón con un parámetro de cadena y establecer una devolución de llamada para el botón.
Después de crear la clase holder, necesitamos una clase adapter. Un adaptador es un puente entre los datos y la vista de RecyclerView. Crea un nuevo archivo dentro del directorio java/package/ llamado StoryAdapter.kt y añade el siguiente código en este archivo:
package com.example.storiesapplication
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
class StoryAdapter(private val dataset: Array<VideoJson>, val onClick: (view: View) -> Unit) : RecyclerView.Adapter<StoryViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) : StoryViewHolder {
val linearLayout = LayoutInflater.from(parent.context).inflate(R.layout.row, parent, false)
return StoryViewHolder(linearLayout, onClick)
}
override fun onBindViewHolder(holder: StoryViewHolder, position: Int) {
holder.bindModel(dataset[position].name)
}
override fun getItemCount() = dataset.size
}El método onCreateViewHolder infla el row layout y crea una instancia de la clase holder. El método onBindViewHolder establece los datos en una fila específica del RecyclerView, y el método getItemCount devuelve el número de elementos almacenados.
Ahora es el momento de crear el RecyclerView en la MainActivity. Modifique el archivo MainActivity.kt sustituyendo el contenido por lo siguiente:
package com.example.storiesapplication
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.gson.Gson
import com.opentok.android.*
import okhttp3.*
import kotlinx.coroutines.*
const val REQUEST_CODE_CREATE_STORY = 1
const val REQUEST_CODE_VIEW_STORY = 2
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: RecyclerView.Adapter<*>
private lateinit var viewManager: RecyclerView.LayoutManager
private val client = OkHttpClient()
private var videosMap = mutableMapOf<String, String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fab: View = findViewById(R.id.fab)
fab.setOnClickListener { view ->
val self = this
CoroutineScope(Dispatchers.IO).launch {
val deferredToken = async { getToken() }
val results = deferredToken.await()
withContext(Dispatchers.Main) {
val intent = Intent(self, CreatingStoryActivity::class.java).apply {
putExtra("token", results)
}
startActivityForResult(intent, REQUEST_CODE_CREATE_STORY)
}
}
}
loadUpVideos()
}
fun loadUpVideos() {
val self = this
viewManager = LinearLayoutManager(this)
CoroutineScope(Dispatchers.IO).launch {
val deferredVideos = async { getVideos() }
val videosList = deferredVideos.await()
videosMap = mutableMapOf<String, String>()
for (video in videosList) {
videosMap.put(video.name, video.archive_id)
}
viewAdapter = StoryAdapter(videosList) { view: View ->
val button: Button = view as Button
CoroutineScope(Dispatchers.IO).launch {
val archiveId = videosMap[button.text.toString()]
withContext(Dispatchers.Main) {
val intent = Intent(self, ViewingStoryActivity::class.java).apply {
putExtra("archive_id", archiveId)
}
startActivityForResult(intent, REQUEST_CODE_VIEW_STORY)
}
}
}
withContext(Dispatchers.Main) {
recyclerView = findViewById<RecyclerView>(R.id.listview).apply {
setHasFixedSize(true)
layoutManager = viewManager
adapter = viewAdapter
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode== REQUEST_CODE_CREATE_STORY) {
loadUpVideos()
}
}
suspend fun getToken(): Array<String> {
var request = Request.Builder().url("${getString(R.string.SERVER)}/stories/token").build()
client.newCall(request).execute().use { response ->
val string = response.body!!.string()
val gson = Gson()
val tokenJson = gson.fromJson(string, TokenJson::class.java)
val session_id = tokenJson.session
val token = tokenJson.token
val api_key = tokenJson.api_key
return arrayOf<String>(api_key, token, session_id)
}
}
suspend fun getVideos(): Array<VideoJson> {
var request = Request.Builder().url("${getString(R.string.SERVER)}/stories/videos-list").build()
client.newCall(request).execute().use { response ->
val string = response.body!!.string()
val gson = Gson()
val videosJson = gson.fromJson(string, Array<VideoJson>::class.java)
return videosJson
}
}
}
class TokenJson(
val token: String,
val session: String,
val api_key: String
)
class VideoJson(
val name: String,
val archive_id: String
)El método onCreate añade una llamada de retorno al botón flotante de esta Actividad, que llamará al método getToken para obtener el token, la sesión y la clave API antes de pasarlos a la CreatingStoryActivity Actividad. El RecyclerView también carga la lista de videos, y para cada fila, agregamos un callback para llamar a la Activity. ViewingStoryActivity Actividad.
A continuación, tenemos que crear el archivo CreatingStoryActivity.kt en el directorio app/src/main/java/com/example/storiesapplication (el mismo directorio donde reside el archivo MainActivity.kt ), que es la Actividad que publica un video en el servidor OpenTok. Copie el siguiente código en este nuevo archivo::
package com.example.storiesapplication
import android.Manifest
import android.opengl.GLSurfaceView
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import com.opentok.android.*
import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.Request
import pub.devrel.easypermissions.AfterPermissionGranted
import pub.devrel.easypermissions.EasyPermissions
class CreatingStoryActivity : AppCompatActivity(), Session.SessionListener, PublisherKit.PublisherListener {
private var mPublisherViewContainer: FrameLayout? = null
private var mPublisher: Publisher? = null
private val client = OkHttpClient()
companion object {
private val LOG_TAG = "android-stories"
const val RC_VIDEO_APP_PERM = 124
private var mSession: Session? = null
}
private var token: String? = null
private var apiKey: String? = null
private var sessionId: String? = null
private var archiveId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_creating_story)
val message = intent.getStringArrayExtra("token")
message?.let {
apiKey = it[0]
token = it[1]
sessionId = it[2]
requestPermissions()
}
val button = findViewById<Button>(R.id.publishbutton)
button.setOnClickListener {
mSession!!.unpublish(mPublisher)
CoroutineScope(Dispatchers.IO).launch {
val deferredStopArchive = async { stopArchive() }
deferredStopArchive.await()
withContext(Dispatchers.Main) {
finish()
}
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
@AfterPermissionGranted(RC_VIDEO_APP_PERM)
private fun requestPermissions() {
val perms = arrayOf<String>(Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
if (EasyPermissions.hasPermissions(this, *perms)) {
mPublisherViewContainer = findViewById(R.id.publisher)
mSession = Session.Builder(this, this.apiKey, this.sessionId).build()
mSession?.let {
it.setSessionListener(this)
it.connect(this.token)
}
} else {
EasyPermissions.requestPermissions(this, "This app needs access to your camera and mic to make video calls", RC_VIDEO_APP_PERM, *perms)
}
}
suspend fun startArchive(): Unit {
var request = Request.Builder().url("${getString(R.string.SERVER)}/stories/video-start-archive/${sessionId}").build()
client.newCall(request).execute().use { response ->
val string = response.body!!.string()
archiveId = string
}
}
suspend fun stopArchive(): Unit {
var request = Request.Builder().url("${getString(R.string.SERVER)}/stories/video-stop-archive/${archiveId}").build()
client.newCall(request).execute()
}
override fun onConnected(session: Session?) {
Log.i(LOG_TAG, "Session Connected")
mPublisher = Publisher.Builder(this).build()
mPublisher?.let {
it.setPublisherListener(this)
it.renderer.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL)
mPublisherViewContainer!!.addView(it.view)
if (it.view is GLSurfaceView) {
(it.view as GLSurfaceView).setZOrderOnTop(true)
}
mSession!!.publish(mPublisher)
CoroutineScope(Dispatchers.IO).launch {
val deferredStartArchive = async { startArchive() }
deferredStartArchive.await()
}
}
}
override fun onDisconnected(session: Session?) {
Log.i(LOG_TAG, "Session Disconnected")
}
override fun onStreamReceived(session: Session?, stream: Stream?) {
Log.i(LOG_TAG, "Stream Received")
}
override fun onStreamDropped(session: Session?, stream: Stream?) {
Log.i(LOG_TAG, "Stream Dropped")
}
override fun onError(publisherKit: Session?, opentokError: OpentokError?) {
opentokError?.let {
Log.e(LOG_TAG, "Session error: " + opentokError.getMessage())
}
}
override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError?) {
opentokError?.let {
Log.e(LOG_TAG, "Publisher error: " + opentokError.getMessage())
}
}
override fun onStreamCreated(publisherKit: PublisherKit?, stream: Stream?) {
Log.i(LOG_TAG, "Publisher onStreamCreated")
}
override fun onStreamDestroyed(publisherKit: PublisherKit?, stream: Stream?) {
Log.i(LOG_TAG, "Publisher onStreamDestroyed")
}
}En el onCreate solicitamos permisos para la cámara, Internet y la grabación de audio. Si tenemos los permisos, creamos un objeto de sesión usando el token, la sesión y la clave API. También tenemos que establecer el oyente para este objeto de sesión. También establecemos un callback para el botón en esta Actividad. El botón enviará una solicitud para dejar de archivar el vídeo.
Tenemos muchos métodos requeridos por el oyente de sesión. El más importante es el método onConnected que será llamado si nuestra sesión se ha conectado al servidor OpenTok.
Creamos un objeto editor y establecemos el FrameLayout como vista del editor. También tenemos que conectar nuestro objeto session a este objeto publisher. Luego creamos un objeto request para comenzar a archivar el video.
A continuación, debemos crear el archivo ViewingStoryActivity.kt en el directorio app/src/main/java/com/example/storiesapplication (el mismo directorio donde se encuentra el archivo MainActivity.kt archivo), que es la Actividad que ve un Video, así que agregue el siguiente código en este archivo::
package com.example.storiesapplication
import android.os.Bundle
import android.webkit.WebView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import okhttp3.OkHttpClient
import okhttp3.Request
class ViewingStoryActivity : AppCompatActivity() {
private val client = OkHttpClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_viewing_story)
val webView: WebView = findViewById(R.id.webview)
val message = intent.getStringExtra("archive_id")
message?.let {
CoroutineScope(Dispatchers.IO).launch {
val deferredVideoUrl = async { getVideoUrl(it) }
val videoUrl = deferredVideoUrl.await()
withContext(Dispatchers.Main) {
webView.loadUrl(videoUrl)
}
}
}
}
suspend fun getVideoUrl(archiveId: String): String {
var request = Request.Builder().url("${getString(R.string.SERVER)}/stories/videos/${archiveId}").build()
client.newCall(request).execute().use { response ->
return response.body!!.string()
}
}
}
En primer lugar, obtenemos la URL del vídeo del id del archivo enviando una petición a nuestra aplicación Django. Luego cargamos la URL en la WebView.
Iniciar la aplicación
Lanza la aplicación. Recuerda usar Ngrok o desplegar la aplicación Django en la nube si quieres usar un dispositivo Android. Al principio, verás una pantalla vacía y un botón flotante.

Pulsa el botón flotante y el dispositivo transmitirá un vídeo desde tu cámara al servidor de OpenTok. Cuando hayas grabado lo suficiente para tu historia, pulsa el botón "Subir historia" en la pantalla.

Se le redirigirá de nuevo a la pantalla principal, que ahora muestra una lista de sus historias.

Si pulsa la fila de la historia, se le redirigirá a la pantalla de visualización de la historia. Podrá ver el Video que haya grabado previamente.

Conclusión
Esta aplicación dista mucho de ser perfecta. Si te fijas, el vídeo grabado es más corto que la sesión de grabación. Eso ocurre porque lleva tiempo enviar una petición para archivar el vídeo. Además, nuestra lista de historias es un simple RecyclerView. Puedes convertirlas en un scroll horizontal. Cada historia está envuelta en forma de círculo, ¡tal y como se ven las Historias de Instagram! Tampoco tenemos autenticación y autorización. No hay propietario para la historia. Este Video se almacena en el servidor de OpenTok durante algún tiempo. Después, el Video será eliminado. Puedes elegir guardar el Video en otro lugar antes de que eso suceda.