Utilisation de MediaPlayer dans React Native sur OpenHarmony

1. Introduction

Dans cet article, nous allons explorer en détail comment utiliser le composant MediaPlayer pour la lecture audio et vidéo dans un environnement React Native sur OpenHarmony. Nous aborderons les concepts clés, les différences d'implémentation entre les plateformes, et fournirons des exemples de code pour illustrer les points importants.

2. Présentation de MediaPlayer

Dans l'écosystème React Native, MediaPlayer est un terme générique qui englobe les capacités de lecture audio et vidéo. Généralement, on utilise des bibliothèques comme react-native-video ou des modules natifs pour implémenter ces fonctionnalités.

2.1 Adapting React Native to OpenHarmony

React Native offre l'avantage de "Learn Once, Write Anywhere", mais la lecture multimédia présente des défis spécifiques en raison des différences de rendu sous-jacentes entre les systèmes d'exploitation.

  • Différences architecturales : Sur Android, MediaPlayer repose souvent sur ExoPlayer ou le MediaPlayer natif, tandis que sur iOS, c'est AVPlayer. Sur OpenHarmony, le module OH_AVPlayer est utilisé, avec sa propre gestion des états et du cycle de vie.
  • Adaptation du rendu : Pour que React Native fonctionne sur OpenHarmony, il faut pontifier les instructions JavaScript vers le niveau natif. Cela nécessite que les bibliothèques tierces incluent une implémentation native (généralement en C++ ou ArkTS).
  • Gestion des permissions : OpenHarmony a des contrôles de permissions stricts pour l'accès au réseau et aux fichiers. Il est crucial de déclarer correctement ces permissions dans module.json5.

2.2 Principes techniques de base

Le MediaPlayer est essentiellement un automate d'états. Quelle que soit la platfeorme, il suit un flux basique : Idle -> Initialized -> Preparing -> Prepared -> Started -> Paused -> Stopped -> End.

Sur OpenHarmony, les appels de méthodes natives sont effectués via le Bridge (ou TurboModules dans la nouvelle architecture). Par exemple, lorsqu'on appelle seek(1000) en JavaScript, le processus se déroule ainsi :

Comprendre ce flux est crucial pour diagnostiquer les problèmes de latence ou de blocage sur OpenHarmony.

2.3 Scénarios d'utilisation

Les applications de MediaPlayer sont nombreuses :

  • Applications de vidéos courtes : Exigent un chargement rapide et un préchargement par glissement.
  • Éducation en ligne : Supporte la lecture accélérée, le changement de qualité, et la reprise après interruption.
  • Flux en direct : Nécessite une faible latence et supporte HLS (m3u8) ou RTMP.
  • Musique de fond : Doit continuer à jouer même lorsque l'application est en arrière-plan.

3. Mise en pratique de MediaPlayer

Avant de commencer, assurez-vous que votre environnement est configuré correctement.

3.1 Préparation de l'environnement

  • Node.js: >= 16.x
  • React Native: >= 0.72.x
  • OpenHarmony SDK: API Level 9 et plus
  • Bibliothèques : npm install react-native-video

Après l'installation, configurez les liens des modules natifs dans votre projet OpenHarmony.

3.2 Implémentation d'un lecteur vidéo simple

Voici un exemple minimal d'un lecteur vidéo avec des fonctionnalités de base :

import React, { useRef, useState } from 'react';
import { View, Text, StyleSheet, SafeAreaView, TouchableOpacity } from 'react-native';
import Video from 'react-native-video';

const SimpleMediaPlayer = () => {
  const videoPlayer = useRef(null);
  const [paused, setPaused] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);

  const videoSource = {
    uri: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4',
  };

  const onLoad = (meta) => {
    console.log('Video Loaded:', meta.duration);
    setDuration(meta.duration);
  };

  const onProgress = (data) => {
    setCurrentTime(data.currentTime);
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.videoContainer}>
        <Video
          source={videoSource}
          ref={videoPlayer}
          resizeMode="contain"
          repeat={false}
          paused={paused}
          style={styles.video}
          onLoad={onLoad}
          onProgress={onProgress}
          onEnd={() => setPaused(true)}
          volume={1.0}
          rate={1.0}
        />
      </View>

      <View style={styles.controls}>
        <TouchableOpacity onPress={() => setPaused(!paused)}>
          <Text style={styles.controlText}>{paused ? '▶️ Play' : '⏸️ Pause'}</Text>
        </TouchableOpacity>
        <Text style={styles.timeText}>
          Time: {Math.floor(currentTime)} / {Math.floor(duration)}
        </Text>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  videoContainer: {
    width: '100%',
    height: 300,
    backgroundColor: '#000',
    justifyContent: 'center',
    alignItems: 'center',
  },
  video: {
    width: '100%',
    height: '100%',
  },
  controls: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    padding: 20,
    backgroundColor: '#f0f0f0',
  },
  controlText: {
    fontSize: 18,
    color: '#333',
    fontWeight: 'bold',
  },
  timeText: {
    fontSize: 16,
    color: '#666',
  },
});

export default SimpleMediaPlayer;

Points clés
  1. Attriubt source : Spécifie l'URL ou le chemin local du fichier vidéo.
  2. Attribut resizeMode : Contrôle le mode de remplissage de la vidéo.
  3. État paused : Permet de contrôler la lecture et la pause.
  4. Référence ref : Utilisée pour accéder aux méthodes du composant.

4. Fonctionnalités avancées

Pour des fonctionnalités plus complexes, comme le contrôle de la progression et la lecture accélérée, voici un exemple amélioré :

import React, { useRef, useState, useEffect } from 'react';
import { View, StyleSheet, SafeAreaView, Text, TouchableOpacity } from 'react-native';
import Video from 'react-native-video';
import Slider from '@react-native-community/slider';

const AdvancedMediaPlayer = () => {
  const videoRef = useRef(null);
  const [paused, setPaused] = useState(true);
  const [currentTime, setCurrentTime] = useState(0);
  const [seekableDuration, setSeekableDuration] = useState(0);
  const [rate, setRate] = useState(1.0);
  const [isFullscreen, setIsFullscreen] = useState(false);

  const formatTime = (seconds) => {
    const mins = Math.floor(seconds / 60);
    const secs = Math.floor(seconds % 60);
    return `${mins < 10 ? '0' : ''}${mins}:${secs < 10 ? '0' : ''}${secs}`;
  };

  const onSliderValueChange = (value) => {
    setCurrentTime(value);
  };

  const onSlidingComplete = (value) => {
    videoRef.current?.seek(value);
    setCurrentTime(value);
  };

  const changeRate = (newRate) => {
    setRate(newRate);
  };

  return (
    <SafeAreaView style={[styles.container, isFullscreen && styles.fullscreenContainer]}>
      <View style={styles.videoWrapper}>
        <Video
          source={{ uri: 'https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4' }}
          ref={videoRef}
          resizeMode="contain"
          paused={paused}
          rate={rate}
          volume={1.0}
          muted={false}
          style={isFullscreen ? styles.fullscreenVideo : styles.video}
          onLoad={(data) => setSeekableDuration(data.duration)}
          onProgress={(data) => {
            if (Math.abs(data.currentTime - currentTime) > 0.5) {
              setCurrentTime(data.currentTime);
            }
          }}
          onEnd={() => {
            setPaused(true);
            setCurrentTime(0);
          }}
          onError={(e) => console.error('Video Error:', e)}
        />
      </View>

      <View style={styles.controlsContainer}>
        <View style={styles.progressContainer}>
          <Text style={styles.timeText}>{formatTime(currentTime)}</Text>
          <Slider
            style={styles.slider}
            minimumValue={0}
            maximumValue={seekableDuration > 0 ? seekableDuration : 1}
            value={currentTime}
            onValueChange={onSliderValueChange}
            onSlidingComplete={onSlidingComplete}
            minimumTrackTintColor="#1EB1FC"
            maximumTrackTintColor="#d3d3d3"
            thumbTintColor="#1EB1FC"
          />
          <Text style={styles.timeText}>{formatTime(seekableDuration)}</Text>
        </View>

        <View style={styles.buttonsContainer}>
          <TouchableOpacity onPress={() => setPaused(!paused)}>
            <Text style={styles.buttonText}>{paused ? 'Play' : 'Pause'}</Text>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => changeRate(rate === 1.0 ? 1.5 : 1.0)}>
            <Text style={styles.buttonText}>{rate}x</Text>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => setIsFullscreen(!isFullscreen)}>
            <Text style={styles.buttonText}>{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}</Text>
          </TouchableOpacity>
        </View>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
    justifyContent: 'center',
  },
  fullscreenContainer: {
    ...StyleSheet.absoluteFillObject,
    zIndex: 999,
  },
  videoWrapper: {
    width: '100%',
    height: 220,
    backgroundColor: 'black',
  },
  video: {
    width: '100%',
    height: '100%',
  },
  fullscreenVideo: {
    ...StyleSheet.absoluteFillObject,
  },
  controlsContainer: {
    padding: 10,
    backgroundColor: '#1c1c1c',
  },
  progressContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 10,
  },
  slider: {
    flex: 1,
    marginHorizontal: 10,
  },
  timeText: {
    color: '#fff',
    fontSize: 12,
    width: 40,
  },
  buttonsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
  },
  buttonText: {
    color: '#fff',
    fontSize: 14,
    fontWeight: '600',
  },
});

export default AdvancedMediaPlayer;

Points clés
  1. Attribut rate : Contrôle la vitesse de lecture.
  2. Opération seek : Permet de naviguer dans la vidéo.
  3. Logique plein écran : Modifie le style du composant pour simuler le plein écran.

5. Considérations spécifiques à OpenHarmony

5.1 Compatibilité de décodage matériel

  • Problème : La prise en charge du décodage matériel varie selon les appareils.
  • Solution : Fournir plusieurs formats de flux vidéo et détecter les capacités de l'appareil.

5.2 Limites de la lecture en arrière-plan

  • Configuration : Demander la permission ohos.permission.KEEP_BACKGROUND_RUNNING.
  • API de tâches longues : Utiliser les API de tâches longues pour la lecture en arrière-plan prolongée.

5.3 Protocoles de streaming supportés

Protocole Android iOS OpenHarmony Remarques
HLS (m3u8) Supporté Nativement supporté Supporté (API 9+) Bon support, mais DRM peut nécessiter des versions spécifiques
MP4 Supporté Supporté Supporté H.264 offre la meilleure compatibilité
DASH ⚠️ Nécessite ExoPlayer ⚠️ Nécessite une bibliothèque ⚠️ Support limité Recommandé de transcoder en HLS
RTMP Non recommandé Non recommandé Non recommandé WebRTC recommandé pour la faible latence

5.4 Problèmes de couche de rendu

  • Phénomène : Les vidéos peuvent masquer d'autres composants.
  • Solution : Éviter de superposer des popups complexes sur la vidéo, ou ajuster l'ordre Z des couches natives.

6. Optimisation des performances et comparaison

Pour évaluer les performances, voici un tableau de comparaison basé sur des tests sur Huawei Mate 60 Pro (OpenHarmony 4.0) et iPhone 13 (iOS 17).

Métrique OpenHarmony (RN) iOS (RN) Analyse des différences
Temps de chargement de la première image 450ms 300ms Initialisation légèrement plus lente sur OpenHarmony
Latence de seek 300ms 150ms Performances de seek légèrement inférieures
Occupation CPU (lecture) 12% 8% Coût de rendu logiciel plus élevé
Occupation mémoire 85MB 70MB Coût de pontage Bridge
Chauffe Chaude Fraîche Plus notable lors de la lecture de vidéos 4K

7. Conclusion

Cet article a couvert l'utilisation de MediaPlayer dans React Native sur OpenHarmony, en mettant l'accent sur les principes de base, les fonctionnalités avancées, et les considérations spécifiques à la plateforme. Ces connaissances vous permettront de construire des lecteurs multimédias robustes et performants.

Étiquettes: React Native OpenHarmony MediaPlayer Video Audio

Publié le 7 octobre à 16h02