0Pricing
Android Academy · درس

تشغيل الصوت والفيديو

تشغيل الوسائط البسيط في Android

تشغيل الصوت والفيديو درس مجاني في Android Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Android Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Android Academy 4 دروس في المجموع.

ما يتجاوز الصور: تشغيل الوسائط

يعرض Coil الصور بطريقة رائعة، لكن التطبيقات تشغّل الصوت والفيديو أيضًا: نغمة إشعار، أو ملفًا صوتيًا، أو مقطعًا في الخلفية، أو موجز فيديو. يوفر Android واجهات برمجة تطبيقات مخصصة للوسائط.

المكتبة الحديثة الموصى بها هي Media3 (ExoPlayer). فهي تتعامل مع البث والتنسيقات والتخزين المؤقت المؤقت وعناصر التحكم في التشغيل بطريقة أفضل بكثير من MediaPlayer القديم. ستشغّلون في هذا الدرس صوتًا وفيديو بسيطين في تطبيق Compose.

إضافة تبعيات Media3

تنقسم Media3 إلى وحدات. تحتاج معظم التطبيقات إلى media3-exoplayer (محرك التشغيل) وmedia3-ui (عناصر عرض جاهزة لمشغّل الوسائط). وبالنسبة إلى فيديو Compose، يمكنكم أيضًا استخدام PlayerView عبر AndroidView.

// build.gradle.kts (module level)
dependencies {
    implementation("androidx.media3:media3-exoplayer:1.5.0")
    implementation("androidx.media3:media3-ui:1.5.0")
}

إنشاء ExoPlayer

يُنشأ ExoPlayer من Context. وتمررون إليه MediaItem، وهو عنوان URL أو ملف محلي، ثم تستدعون prepare() وتبدؤون التشغيل. ويعمل ExoPlayer باستخدام خيوطه الخاصة، لذلك لا يحجب واجهة المستخدم.

ومن المهم أن المشغّل يحتفظ بموارد النظام، ولذلك يجب تحريره، وسنتناول ذلك بعد قليل.

import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

fun createPlayer(context: Context, url: String): ExoPlayer {
    val player = ExoPlayer.Builder(context).build()
    player.setMediaItem(MediaItem.fromUri(url))
    player.prepare()
    player.playWhenReady = true
    return player
}

تشغيل الصوت

بالنسبة إلى تشغيل الصوت فقط، يكون الإعداد مماثلًا تمامًا؛ فما عليكم سوى توجيه MediaItem إلى عنوان URL صوتي بصيغة mp3 أو aac أو غيرها. لا يوجد عرض لعرضه، لذا تتحكمون فيه من خلال كائن المشغّل: play() وpause() وseekTo().

وهذا يكفي لإنشاء مشغّل ملفات صوتية أو موسيقى.

import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

fun playPodcast(player: ExoPlayer, audioUrl: String) {
    player.setMediaItem(MediaItem.fromUri(audioUrl))
    player.prepare()
    player.play()
}

fun togglePause(player: ExoPlayer) {
    if (player.isPlaying) player.pause() else player.play()
}

الاحتفاظ بالمشغّل في Compose

في Compose، ينبغي إنشاء المشغّل مرة واحدة والاحتفاظ به عبر عمليات إعادة التركيب. استخدموا remember حتى لا يُنشأ مشغّل جديد عند كل عملية إعادة تركيب.

واجمعوا ذلك مع تنظيف مناسب، في المشهد التالي، حتى لا تسرّبوا الموارد.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun rememberPlayer(url: String): ExoPlayer {
    val context = LocalContext.current
    return remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(url))
            prepare()
        }
    }
}

تحرير المشغّل

يُبقي المشغّل برامج الترميز والمخازن المؤقتة وتركيز الصوت نشطة. وإذا نسيتم تحريره، فستتسببون في تسرّب الذاكرة، وقد يستمر التشغيل بعد مغادرة الشاشة. في Compose، ينفّذ DisposableEffect عملية التنظيف عندما يغادر composable التركيب.

استدعوا دائمًا player.release() هناك.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun ManagedPlayer(player: ExoPlayer) {
    DisposableEffect(player) {
        onDispose {
            player.release()
        }
    }
    // ... use player here
}

عرض الفيديو باستخدام PlayerView

توفر Media3 عنصر PlayerView جاهزًا مع عناصر التحكم. لا يحتوي Compose على composable أصلي للفيديو، لذا تضمنون View باستخدام AndroidView وتربطونه بالمشغّل.

وبذلك تحصلون تلقائيًا على التشغيل والإيقاف المؤقت وشريط التقديم ووضع ملء الشاشة.

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

@Composable
fun VideoPlayer(player: ExoPlayer, modifier: Modifier = Modifier) {
    AndroidView(
        factory = { context ->
            PlayerView(context).apply {
                this.player = player
            }
        },
        modifier = modifier
    )
}

شاشة فيديو مكتملة

لنجمع كل ذلك معًا: أنشئوا المشغّل، واحتفظوا به، واربطوه بـ PlayerView، ثم حرّروه عند التخلص منه. هذه شاشة فيديو مستقلة يمكنكم إعادة استخدامها.

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView

@Composable
fun VideoScreen(videoUrl: String) {
    val context = LocalContext.current
    val player = remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(videoUrl))
            prepare()
            playWhenReady = true
        }
    }
    DisposableEffect(Unit) { onDispose { player.release() } }

    AndroidView(
        factory = { PlayerView(it).apply { this.player = player } },
        modifier = Modifier.fillMaxWidth().height(220.dp)
    )
}

تتبّع حالة التشغيل

لتحديث واجهة المستخدم، مثل أيقونة التشغيل أو الإيقاف المؤقت وشريط التقدم، استمعوا إلى المشغّل باستخدام Player.Listener. فهو يبلّغ عن تغيّرات الحالة، مثل التخزين المؤقت المؤقت والاستعداد والانتهاء، بالإضافة إلى ما إذا كان التشغيل نشطًا.

أضيفوا المستمع عند ظهور الشاشة وأزيلوه عند التخلص منها.

import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer

fun observe(player: ExoPlayer, onPlaying: (Boolean) -> Unit): Player.Listener {
    val listener = object : Player.Listener {
        override fun onIsPlayingChanged(isPlaying: Boolean) {
            onPlaying(isPlaying)
        }
        override fun onPlaybackStateChanged(state: Int) {
            when (state) {
                Player.STATE_BUFFERING -> { /* show spinner */ }
                Player.STATE_READY -> { /* ready */ }
                Player.STATE_ENDED -> { /* finished */ }
            }
        }
    }
    player.addListener(listener)
    return listener
}

الإيقاف المؤقت عند انتقال التطبيق إلى الخلفية

بالنسبة إلى الفيديو المضمّن، وليس تطبيقات الموسيقى، سترغبون عادةً في إيقاف التشغيل مؤقتًا عندما يغادر المستخدم الشاشة واستئنافه عند عودته. يمكنكم ربط التشغيل بدورة حياة Compose من خلال مراقبة Lifecycle والإيقاف المؤقت عند ON_PAUSE.

يمنع ذلك تشغيل فيديو لا يستطيع المستخدم رؤيته، ويوفر طاقة البطارية.

import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.exoplayer.ExoPlayer

@Composable
fun PauseWithLifecycle(player: ExoPlayer) {
    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_PAUSE -> player.pause()
                Lifecycle.Event.ON_RESUME -> player.play()
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
    }
}

الصوت في الخلفية باستخدام MediaSession

بالنسبة إلى الصوت الذي ينبغي أن يستمر تشغيله عند انتقال التطبيق إلى الخلفية، مثل الموسيقى والملفات الصوتية، غلّفوا التشغيل داخل MediaSessionService. فهذا يدمجه مع عناصر تحكم النظام وشاشة القفل وأزرار Bluetooth.

يتطلب ذلك عملًا إضافيًا، لكن الفكرة الأساسية هي إتاحة المشغّل من خلال MediaSession.

import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService

class PlaybackService : MediaSessionService() {
    private var session: MediaSession? = null

    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        session = MediaSession.Builder(this, player).build()
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = session

    override fun onDestroy() {
        session?.run { player.release(); release() }
        session = null
        super.onDestroy()
    }
}

اختبار سريع

في شاشة Compose تحتوي على ExoPlayer، ما واجهة Compose البرمجية التي ينبغي استخدامها لتحرير المشغّل عند مغادرة الشاشة للتركيب؟

مراجعة

لقد أضفتم تشغيل الصوت والفيديو:

  • استخدموا Media3 ExoPlayer، البديل الحديث لـ MediaPlayer.
  • أنشئوا مشغّلًا، واضبطوا MediaItem، واستدعوا prepare() ثم play().
  • في Compose، احتفظوا بالمشغّل باستخدام remember وحرّروه في onDispose الخاص بـ DisposableEffect.
  • اعرضوا الفيديو من خلال تضمين PlayerView باستخدام AndroidView.
  • تتبّعوا الحالة باستخدام Player.Listener، واستخدموا MediaSessionService للصوت في الخلفية.

بهذا تكتمل دورة Images & Media Loading with Coil. يمكنكم الآن تحميل الصور بسلاسة وتشغيل الوسائط بالطريقة الحديثة في Android.

الأسئلة الشائعة

هل درس «تشغيل الصوت والفيديو» مجاني؟

نعم — نص درس «تشغيل الصوت والفيديو» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Android Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Android Academy 4 دروس في المجموع.

ماذا ستتعلم في «تشغيل الصوت والفيديو»؟

تشغيل الوسائط البسيط في Android تتمرن على Android Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Android Academy؟

لا تُشترط خبرة سابقة. Android Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «تشغيل الصوت والفيديو»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Android Academy هذا؟

نعم. كل درس في Android Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تحميل الصور باستخدام Coil
  2. العناصر النائبة وحالات الخطأ
  3. التخزين المؤقت والأداء
  4. تشغيل الصوت والفيديو
← العودة إلى Android Academy