
Partager:
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
Construire des histoires de style médias sociaux avec Android et Python
Temps de lecture : 20 minutes
Instagram dispose d'une fonctionnalité populaire, qui s'appelle Stories, fortement inspirée de Snapchat. Les Stories permettent aux utilisateurs de créer une vidéo ou une image fixe qui disparaîtra dans un court laps de temps (24 heures). Plus tard, d'autres plateformes de médias sociaux ont également développé cette fonctionnalité, comme Linkedin et Twitter.
Dans ce tutoriel, nous allons construire cette fonctionnalité pour Android avec l'API Video de Vonage, nous aurons également besoin d'un serveur pour gérer les sessions et les tokens pour le client, qui sera construit en utilisant Python.
Conditions préalables
Appareil Android (facultatif)
Ngrok (facultatif)
Construction du serveur
Pour commencer, nous allons construire un serveur avec lequel notre application mobile pourra communiquer. Ce serveur utilisera Djangoun framework web Python.
Installation des dépendances
Tout d'abord, créez un environnement virtuel Python en exécutant les deux commandes suivantes :
Ensuite, nous pouvons installer les dépendances pour notre serveur backend :
La commande ci-dessus installera Django, et Opentok le SDK Python de Vonage Video.
Création d'un projet et d'une application
Initialisez votre projet Django en exécutant la commande suivante :
Avant de nous lancer dans l'écriture du code, nous devons créer au moins une application à l'intérieur du projet Django. Un projet Django peut être composé de plusieurs applications. Par exemple, le projet A peut être une application Stories, tandis que le projet B peut être une application de messagerie ou une application de commerce électronique. Tout d'abord, changez votre répertoire courant en répertoire de projet, puis initialisez une nouvelle application à l'aide des deux commandes indiquées ci-dessous :
Il n'est pas nécessaire d'exécuter la commande suivante car nous n'utiliserons pas de base de données dans ce tutoriel. Cependant, un avertissement persistera à moins que nous n'exécutions la commande de migration, alors exécutez ce qui suit pour arrêter ces avertissements.
Pour s'assurer que notre application web Django fonctionne, vous pouvez exécuter le script webserver.
Vous pouvez accéder à votre application web sur localhost avec le port 8000. Ce qui, dans votre navigateur, ressemblera à http://localhost:8000. Si vous vous rendez à cette URL, vous serez accueilli par un message de réussite et une image de fusée.
Variables d'environnement
Notre code devra utiliser la clé et le secret de l'API Video de Vonage (également connue sous le nom d'OpenTok), qui ne doivent jamais être codés en dur dans votre application. Pour augmenter la sécurité de l'application, vous pouvez utiliser des variables d'environnement. Dans votre terminal, vous pouvez définir vos deux valeurs comme indiqué dans les exemples ci-dessous. Veillez à remplacer xxxxxx par vos valeurs. Vous pouvez obtenir ces valeurs pour la clé et le secret de l'API dans la documentation de l'API vidéo de Vonage. Video API de Vonage.
Créer les vues
Les vues sont les modèles qui sont rendus dans votre application Django. Ouvrez le fichier storiesserver/storiesapp/views.py et remplacez son contenu par ce qui suit :
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 première partie de ce code crée une instance OpenTok, qui utilisera nos variables d'environnement prédéfinies pour la clé et le secret de l'API.
Nous avons alors deux variables globales, videos et index. Elles sont comme une mini-base de données en mémoire qui contient les informations relatives à nos histoires (ou vidéos).
Dans la première méthode, nous générons notre session et notre jeton avec la méthode get_token méthode. Pour créer une session, nous utilisons la méthode create_session de l'instance OpenTok. Elle accepte deux paramètres : un mode média et un mode archive. Nous utilisons la valeur MediaModes.routed pour le mode média car nous publions une vidéo en tant qu'histoire, et non pour le chat vidéo. MediaModes.routed signifie que nous envoyons la vidéo au serveur plutôt qu'à un autre client. Nous utilisons la valeur ArchiveModes.manual pour le mode archive parce que nous voulons archiver (enregistrer) la vidéo manuellement afin d'obtenir l'identifiant de l'archive. Cet identifiant est important lorsque nous voulons obtenir l'enregistrement vidéo. Si ce n'est pas le cas, nous devons l'itérer à partir de la liste des archives, ce qui n'est pas pratique. Pour générer le jeton, nous utilisons la méthode generate_token de l'instance de session. Nous passons la valeur de l'heure d'expiration en tant que paramètre dans cette méthode. Enfin, nous envoyons la session, le jeton et la clé API au client.
Dans la deuxième méthode, la méthode video_stream nous obtenons l'URL de la vidéo à partir de l'identifiant de l'archive. Si vous remarquez, cette méthode a un paramètre supplémentaire en plus du paramètre habituel, request. La façon dont nous obtenons l'identifiant de l'archive se fait dans une autre méthode.
Dans la troisième méthode, la méthode videos_list nous envoyons la liste de nos vidéos au client. Il s'agit essentiellement de notre videos enveloppée dans du JSON.
Dans la quatrième méthode, la méthode video_start_archive nous archivons la Video. Cette méthode comporte un paramètre supplémentaire, session_id. Nous utilisons la méthode start_archive de l'instance OpenTok. Elle accepte une session OpenTok. Après avoir exécuté cette méthode, notre session vidéo sera enregistrée. La méthode start_archive renvoie l'identifiant de l'archive. Nous le stockons dans la variable videos . Nous l'enregistrons dans la variable Outre l'identifiant de l'archive, nous générons un nom agréable pour cette vidéo, comme "Story 1", "Story 2", etc.
Dans la cinquième méthode, la méthode video_stop_archive nous arrêtons d'archiver la Video. Cette méthode comporte un paramètre supplémentaire, archive_id. En d'autres termes, nous arrêtons d'enregistrer la Video. Nous utilisons la méthode stop_archive de l'instance OpenTok. Elle accepte l'identifiant de l'archive comme paramètre.
La dernière méthode, la méthode homepage est utilisée à des fins de test pour vérifier si l'application web de Django est accessible ou non.
Créer un mappage d'URL
Nous devons ensuite créer une correspondance entre les URL et ces méthodes de visualisation. Créez le fichier storiesserver/storiesapp/urls.py et copier l'exemple ci-dessous dans le fichier :
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'),
]La méthode path accepte trois arguments :
Chemin d'accès à l'URL Méthode d'accès Nom de l'itinéraire à des fins de référence.
Par exemple, le deuxième chemin fait correspondre l'URL token à la méthode get_token dans les vues.
Du côté serveur de l'application Django, nous devons modifier le fichier urls.py pour qu'il corresponde aux URL des récits, il faut donc localiser le fichier : storiesserver/storiesserver/urls.py et remplaçons le contenu de ce fichier par :
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('stories/', include('storiesapp.urls')),
]Dans ce fichier, nous faisons correspondre le fichier URL de l'application à l'adresse stories/ URL. Par exemple, avec l'URL token nous aurions l'URL complète suivante : http://localhost:8000/stories/token.
Ngrok
Pour exposer l'application Django à Internet et à nos appareils Android pendant le développement, nous utiliserons Ngrok. Après avoir connecté votre Account, nous pouvons démarrer un tunnel HTTP qui redirige vers le port de notre application web Django, 8000 :
Lorsque le tunnel a été créé, vous verrez l'URL publique qui ressemble à quelque chose comme : https://xxxxxxxx.ngrok.io. Vous devez ensuite modifier le fichier storiesserver/storiesserver/settings.py en ajoutant l'élément "xxxxxxxx.ngrok.io", "10.0.2.2" et "localhost" à la variable ALLOWED_HOSTS à la variable L'adresse 10.0.2.2 est la façon dont l'émulateur Android accède à localhost sur votre ordinateur.
Votre variable ALLOWED_HOSTS devrait ressembler à ceci après la modification :
ALLOWED_HOSTS = ["10.0.2.2", "localhost", "xxxxxxxx.ngrok.io"]Ensuite, pour accéder à l'URL token vous accéderez à cette URL : https://xxxxxxxx.ngrok.io/stories/token.
À ce stade, vous avez construit le composant serveur qui peut générer des jetons, archiver l'enregistrement vidéo et fournir l'URL de l'enregistrement vidéo. Ensuite, vous allez créer une application Android qui servira de client à cette application serveur. Cette application mobile permet d'enregistrer des vidéos et de regarder les enregistrements vidéo.
Côté client
Lancez Android Studio, créez un nouveau projet et sélectionnez "Empty Activity" comme modèle de projet. Pour le SDK minimum, choisissez "API 16 : Android 4.1 (Jelly Bean)" et pour le langage, choisissez "Kotlin".
Dépendances
La première chose à faire lors de la construction d'un projet dans Android Studio est d'ajouter toutes les dépendances nécessaires. Ouvrez le fichier build.gradle au niveau du projet et ajoutez la ligne suivante à l'intérieur du bloc repositories qui se trouve à l'intérieur du bloc allprojects . Ensuite, synchronisez le fichier.
maven { url 'https://tokbox.bintray.com/maven' }La deuxième chose à faire est d'ajouter des dépendances au fichier qui porte le même nom build.gradle mais au niveau de l'application (ou du module). Ajoutez ces lignes à l'intérieur du bloc dependencies à l'intérieur du bloc N'oubliez pas de synchroniser le fichier.
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'Ce projet utilise cinq bibliothèques supplémentaires :
Vonage Video SDK (OpenTok) La bibliothèque OkHttp pour envoyer des requêtes à notre serveur backend, la bibliothèque Gson pour analyser les réponses JSON, la bibliothèque EasyPermissions pour gérer les autorisations lorsque notre application Android demande des permissions pour utiliser la caméra et le microphone, la bibliothèque Coroutines pour gérer le code non bloquant et bloquant de manière pratique.
Configuration de la sécurité du réseau
Cette étape est facultative. Si vous souhaitez tester l'application avec un émulateur et que vous ne voulez pas utiliser Ngrok, vous devez alors créer un fichier de configuration réseau. Créez le répertoire xml à l'intérieur du répertoire res puis créez un nouveau fichier appelé network_security_config.xml à l'intérieur du répertoire xml dans le répertoire. Copiez-y les éléments suivants :
<?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>
Cela signifie que vous pouvez développer l'application sans le protocole HTTPS pour l'URL du backend.
Manifeste Android
Pour activer cette configuration réseau, ouvrez le fichier AndroidManifest.xml et ajoutez l'attribut suivant dans application node :
android:networkSecurityConfig="@xml/network_security_config"Ajoutez ensuite ce qui suit à l'intérieur du nœud manifest à l'intérieur du nœud.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />Nous avons besoin de ces deux ajouts car nous voulons connecter notre application mobile à notre serveur backend.
Pendant que nous sommes dans ce fichier, créons des références à deux activités supplémentaires qui seront créées plus tard. Ajoutez ces lignes après le seul nœud activity .
<activity android:name=".ViewingStoryActivity">
</activity>
<activity android:name=".CreatingStoryActivity">
</activity> Mise en page
Nous avons trois activités, mais nous devons créer trois présentations pour ces activités. Nous devons également créer une mise en page supplémentaire pour la mise en page des lignes dans un RecyclerView utilisé dans l'activité principale.
Tout d'abord, créez la disposition des lignes en créant un nouveau fichier nommé row.xml dans le répertoire app/src/main/res/layout . Copiez le fichier XML suivant dans ce nouveau fichier: :
<?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>Ce qui précède définit la création d'un bouton, qui sera utilisé pour lancer une activité permettant de regarder l'article ou la vidéo.
Ensuite, nous devons créer une mise en page pour afficher une Video. Créez un nouveau fichier nommé activity_viewing_story.xml dans le répertoire layout, et ajoutez le XML suivant au fichier :
<?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>La mise en page contient un élément WebView qui assurera le rendu de la Video.
Ensuite, nous devons créer le fichier activity_creating_story.xml (fichier de présentation). Il s'agit de la mise en page utilisée par l'Activité pour créer une Video. Supprimez le contenu du fichier et ajoutez-y le code suivant :
<?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>Le FrameLayout avec l'ID publisher est utilisé par le SDK vidéo pour afficher la vidéo envoyée au serveur Video. L'objectif du bouton dans cette activité est d'arrêter l'enregistrement de la vidéo et de terminer l'activité.
Enfin, nous devons éditer le fichier activity_main.xml . Ce fichier contient la mise en page utilisée par l'activité principale. Ce fichier contient la mise en page utilisée par l'Activité principale, qui listera toutes les histoires (vidéos) qui ont été créées. Le fichier contiendra également un bouton permettant de lancer l'Activité pour créer une vidéo. Remplacez le contenu de ce fichier par le XML suivant :
<?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> Ajout de l'URL du serveur
Définissons l'URL du serveur dans le fichier strings.xml qui se trouve dans le répertoire values dans le répertoire Pour ce faire, ajoutez ce qui suit à votre resources node :
<string name="SERVER">http://localhost:8000</string>Si vous utilisez Ngrok, remplacez-la par l'exemple suivant (assurez-vous de remplacer xxxxx par votre URL ngrok) :
<string name="SERVER">https://xxxxxx.ngrok.io</string> Classes et activités
Tout d'abord, nous devons créer les classes nécessaires à la création d'un RecyclerView. Un RecyclerView a besoin d'un adaptateur et d'un support. Créez un fichier de support en créant un fichier portant le nom StoryViewHolder.kt dans le répertoire package dans le répertoire Le paquetage sera quelque chose de similaire à com.example.storiesapplication qui se trouve dans le répertoire java . Copiez le code suivant dans ce nouveau fichier :
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
}
}
Pour chaque classe ou fichier Activity que vous créez, veillez à remplacer le package com.example.storiesapplication par votre paquetage si celui-ci est différent.
Il s'agit d'une classe de détenteur standard dans laquelle vous définissez le texte du bouton à l'aide d'un paramètre de type chaîne et définissez un rappel pour le bouton.
Après avoir créé la classe de support, nous avons besoin d'une classe d'adaptateur. Un adaptateur est un pont entre les données et la vue de RecyclerView. Créez un nouveau fichier dans le répertoire java/package/ appelé StoryAdapter.kt et ajoutez le code suivant dans ce fichier :
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
}La méthode onCreateViewHolder gonfle la mise en page row et crée une instance de la classe du support. La méthode onBindViewHolder définit les données sur une ligne spécifique du RecyclerView, et la méthode getItemCount renvoie le nombre d'éléments stockés.
Il est maintenant temps de créer le RecyclerView dans l'activité principale. Modifiez le fichier MainActivity.kt en remplaçant le contenu par ce qui suit :
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
)La méthode onCreate ajoute un rappel au bouton flottant de cette activité, qui appellera la méthode getToken pour obtenir le jeton, la session et la clé API avant de les transmettre à l'activité CreatingStoryActivity Activity. Le RecyclerView charge également la liste des vidéos et, pour chaque ligne, nous ajoutons un rappel pour appeler l'activité ViewingStoryActivity Activité.
Ensuite, nous devons créer le fichier CreatingStoryActivity.kt dans le répertoire app/src/main/java/com/example/storiesapplication (le même répertoire que celui où se trouve le fichier MainActivity.kt ), qui est l'Activité qui publie une vidéo sur le serveur OpenTok. Copiez le code suivant dans ce nouveau fichier: :
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")
}
}Dans la méthode ci-dessus, nous demandons les autorisations pour la caméra, l'internet et l'enregistrement audio. onCreate ci-dessus, nous demandons les autorisations pour la caméra, Internet et l'enregistrement audio. Si nous avons les autorisations, nous créons un objet de session à l'aide du jeton, de la session et de la clé API. Nous devons également définir l'auditeur pour cet objet de session. Nous définissons également un rappel pour le bouton de cette activité. Le bouton enverra une demande d'arrêt de l'archivage de la Video.
De nombreuses méthodes sont nécessaires à l'auditeur de session. La plus importante est la méthode onConnected qui sera appelée si notre session s'est connectée au serveur OpenTok.
Nous créons un objet éditeur et définissons le FrameLayout comme la vue de l'éditeur. Nous devons également connecter notre objet session à cet objet publisher. Nous créons ensuite un objet de requête pour lancer l'archivage de la Video.
Nous devons ensuite créer le fichier ViewingStoryActivity.kt dans le répertoire app/src/main/java/com/example/storiesapplication (le même répertoire que celui où se trouve le fichier MainActivity.kt réside), qui est l'Activité qui visualise une Video, donc ajouter le code suivant dans ce fichier: :
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()
}
}
}
Dans un premier temps, nous obtenons l'URL de la vidéo à partir de l'identifiant de l'archive en envoyant une requête à notre application Django. Ensuite, nous chargeons l'URL dans la WebView.
Lancement de l'application
Lancez l'application. N'oubliez pas d'utiliser Ngrok ou de déployer l'application Django sur le cloud si vous souhaitez utiliser un appareil Android. Au début, vous verrez un écran vide et un bouton flottant.

Appuyez sur le bouton flottant et l'appareil diffusera une vidéo de votre caméra vers le serveur OpenTok. Lorsque vous avez enregistré suffisamment d'images pour votre histoire, appuyez sur le bouton "Upload Story" (télécharger l'histoire) sur l'écran.

Vous serez redirigé vers l'écran principal, qui affiche maintenant une liste de vos histoires.

Si vous appuyez sur la ligne de l'histoire, vous serez redirigé vers l'écran de visualisation de l'histoire. Vous pouvez regarder la vidéo que vous avez enregistrée précédemment.

Conclusion
Cette application est loin d'être parfaite. Si vous le remarquez, la vidéo enregistrée est plus courte que la session d'enregistrement. Cela s'explique par le fait qu'il faut du temps pour envoyer une demande d'archivage de la vidéo. De plus, notre liste d'histoires est un simple RecyclerView. Vous pouvez les convertir en un défilement horizontal. Chaque histoire est enveloppée dans une forme de cercle, tout comme les Stories d'Instagram ! Nous n'avons pas non plus d'authentification ni d'autorisation. Il n'y a pas de propriétaire pour l'histoire. Cette vidéo est stockée dans le serveur OpenTok pendant un certain temps. Après cela, la vidéo sera supprimée. Vous pouvez choisir d'enregistrer la vidéo ailleurs avant que cela ne se produise.