Android

عرض ملفّات الوسائط المتعدّدة في ListView لتشغيلها من خلال MediaPlayer

مقدّمة

يمكن من خلال MediaPlayer class الموجودة في مكتبة نظام Android؛ أن يتمكَّن المبرمج من تشغيل الصوتيّات ومقاطع الفيديو بأنواعها المختلفة بحسب ما يدعمه النظام. سنتعرَّف في هذا المقال على طريقة استخدام ListView widget لعرض الصوتيّات المتاحة، بحيث يمكن للمستخدم اختيار أحدها من القائمة ليتمّ بعد ذلك تشغيلها بواسطة الMediaPlayer class، كما سنقوم بتزويد التطبيق بأزرار لتشغيل الصوتيّات وإيقافها وقوفاً مؤقّتاً (أي Pause) أو وقوفاً تامّاً (Stop).

 

تصميم واجهة التطبيق

سيحتوي التطبيق على ListView widget بالإضافة إلى ثلاثة أزرار: Play، Pause، Stop. يمكن عرض جميع هذه العناصر بالطريقة التي يرغبها المبرمج، ولكن بالنّسبة لي، فقد قمت بوضعها في RelativeLayout، بحيث تعلوها الListView، وتدنوها LinearLayout أفقيّة تحتوي الثلاث أزرار خاصّتنا. ما يلي طريقة ترتيب عناصر الواجهة:

بحيث تظهر الواجهة كالآتي:

يمكن استخدام الكود التالي للحصول على الواجهة:

 

برمجة الواجهة

إن قمت بتصميم الواجهة بنفسك دون اللجوء للكود أعلاه، فلا تنسى تسمية كل عنصر من العناصر بid محدَّد لنتمكَّن من الوصول إليه برمجيّاً. سنقوم أوّلاً بتعريف وصلات لعناصر الواجهة التي سنحتاج للتعامل معها برمجيّاً، وسنستخدم لذلك دالّة ()findViewById. العناصر التي نحتاج التعامل معها هي الlistView بالإضافة للأزرار الثلاثة، بالتالي، سنقوم بالإشارة إلى كلّ عنصر عند بدء التطبيق كما يلي:

 

برمجة الlistView

للاستفادة من الlistView widget والتمكُّن من إضافة العناصر إليها وحذفها، يجب علينا ربطها بما يُسمّى ListAdapter، والتي تكون مرتبطة بArrayList من نوع String تحتوي جميع أسماء الصوتيّات المعروضة للمستخدم. لنتمكَّن من إنشاء ListAdapter، يجب علينا أوّلاً تصميم كيف سيبدو كلّ عُنصر في القائمة، بحيث سنحدِّد كيفيّة ظهوره من خلال ملفّ Layout. لذا، سنقوم بإنشاء ملفّ جديد في مجلَّد “layout” سنسمّيه “listview_item.xml”، وسنضع فيه الكود التالي:

الآن ننتقل مجدّداً لكود الجافا في ملفّ “MainActivity.java” ونقوم بإنشاء ArrayList من نوع String، وستكون صلاحيّات الوصول إليها private، إذ لن نستخدمها خارج الMainActivity class:

لائحة المصفوفات myMedia ستحتوي أسماء ملفّات الصوتيّات المتاحة، والتي ستظهر في الListView widget للمستخدم. الآن سنقوم بتعريف ListAdapter جديدة ونربطها بالLayout (الذي قمنا بإنشائه لشكل عناصر اللائحة) بالإضافة إلى لائحة myMedia والتي ستحتوي عناصر الListView، فسنقوم بتعريفها كالتالي في دالّة onCreate:

 

الآن ما علينا سوى تزويد الlistView widget بالListAdapter الذي ستعتمد عليه في معرفة العناصر التي ستقوم بعرضها، فنقوم باستخدام دالّة setAdapter() لذلك:

الآن، عند إضافة أي نصّ للائحة myMedia، فسيظهر تلقائيّاً في الListView خاصّتنا.

 

عرض الصوتيّات المُراد تشغيلها

سنقوم بإضافة الصوتيّات التي نرغب بعرضها للمستخدم في مجلَّد “/res/raw”، لذا، فسنقوم بإنشاء مجلَّد يحمل الاسم “raw” في مجلَّد “res” والموجود في المجلَّد الرئيسي للمشروع، بعد ذلك سنقوم بإضافة ملفّاتنا الصوتيّة (بصيغة mp3) إليه. بما أنّه لا توجد طريقة مباشرة للوصول إلى جميع ملفّات مجلَّد “/res/raw” برمجيّاً، فسنقوم بإنشاء مصفوفة من نوع String نضع فيها أسماء ملفّاتنا، فمثلاً لو كان لدينا ثلاث ملفّات ألا وهي: sound1.mp3، وsound2.mp3، وsound3.mp3، فسنقوم بتعريف المصفوفة كالتالي:

وبعدها ننسخ هذه المصفوفة إلى لائحة myMedia كالتالي:

 

تشغيل الصوتيّات باستخدام MediaPlayer

سنقوم الآن بتعريف كائن من نوع MediaPlayer سنسمّيه myPlayer، وسنجعل صلاحيّات الوصول إليه هي private، كما سنحتاج إلى متغيِّرين من نوع int، الأوّل لتخزين حالة الMediaPlayer، وسنسمّيه mplayer_status، أمّا الآخر فسيكون لتخزين موقع الملفّ الذي تمَّ اختياره في اللائحة، وسنسمّيه selected_media، وسنجعل قيمتهما الأوّليّة -1، وستتبيَّن لنا أهميّة كِلا المتغيِّرين لاحقاً. بذلك، سيكون لدينا ثلاث تعريفات في MainActivity class كالتالي:

برمجة حالات مشغِّل الصوتيّات

سيتم اختيار الملفّ الصوتي فور النقر عليه من الlistView، وسيتم تشغيله تلقائيّاً، لذا سنقوم باستخدام ()setOnItemClickListener لمعرفة العنصر الذي تمَّ اختياره. بحسب آليّة عمل MediaPlayer class، فإنّه يجب حجز كائن جديد (()new MediaPlayer) في كلّ مرّة نريد تشغيل ملفّ معيَّن، وبعد ذلك، نقوم بتحديد مكان تواجد الملفّ الصوتي باستخدام دالّة ()setDataSource، وبعدها يجب استدعاء دالّة ()prepare لتحضير الملفّ للتشغيل، أخيراً نقوم باستدعاء دالّة ()start. سنقوم بوضع القيمة 1 في المتغيِّر mplayer_status والتي تدلّ على أنَّه يوجد ملفّ قيد التشغيل حاليّاً، وذلك لنتمكَّن من تتبُّع حالة myPlayer. وبذلك، فقد أصبح كود ()setOnItemClickListener كالتالي:

لاحظ أنَّ صيغة الوصول لأيّ ملفّ في مجلَّدات تطبيق الآندرويد الداخليّة هي “///:android.resource” متبوعةً باسم حزمة التطبيق (Package name)، ومن ثمَّ اسم الملفّ، وبما أنَّ ملفّاتنا موجودة في مجلَّد raw، لذا فستكون الصيغة كما هو ظاهر في السطر الثامن من الكود أعلاه.

 

توجد لدينا مشكلة في الكود الذي كتبناه، وهي أنَّ دالّة ()prepare لا يجب استدعاؤها سوى مرّة واحدة بعد اختيار الملفّ الصوتي، وإلّا فسينهار برنامجنا ويتمّ الخروج منه، فماذا لو قام المستخدم بالضغط على الملفّ ذاته مرّتين؟ لحلّ هذه المشكلة، سنقوم بتخزين موقع العنصر (في اللائحة) للملفّ الذي تمَّ اختياره في كل مرّة ينقر فيها المستخدم على ملفّ، وفي حال كان نفس الملفّ الذي تمَّ اختياره في المرّة الأولى، فلن نقوم بفعل أي شيء حينها، وسنستخدم لذلك المتغيِّر selected_media الذي قمنا بتعريفه مُسبقاً، واستخدام جملة if للتأكُّد من الحالة:

 

يُفضلَّ استدعاء دالّة ()release قبل إنشاء كائن جديد (انتبه إلى أنّه من الممكن أن يكون myPlayer ليس معرّفاً بالأصل، وذلك في أوّل مرّة يتم تشغيل ملفّ، ففي حال استدعاء أي دالّة منه فسينهار التطبيق):

الآن تبقّى لنا برمجة الازرار: Play، وPause، وStop، ونقوم بذلك كالتالي:

لاحظ أنَّ القيمة -1 تُمثِّل حالة وقوف الMediaPlayer عن التشغيل وقوفاً تامّاً، والحالة 1 تُمثِّل العكس، أمّا القيمة 0 فهي تُمثِّل حالة كون المُشغِل في حالة الوقوف المؤقَّت (Paused).

الكود النهائي

طُرُق تحسين التطبيق

البحث في مجلّدات الجهاز عن الصوتيّات

إنَّ طريقة استخدام مجلَّد “/res/raw” لن تكون طريقة عمليّة في الواقع، وإنّما ستكون الملفّات موجودة في ذاكرة الجهاز، لذا، سنقوم باستخدام طريقة للبحث عن جميع الملفّات التي تحمل صيغة “mp3.” في جهاز المستخدم. ولكن قبل ذلك، يجب علينا تفعيل أذونات الوصول لذاكرة المستخدم، فنقوم بإضافة السطريين التاليين في الملفّ “AndroidManifest.xml”:

سنقوم الآن بتعريف دالّة جديدة نسمّيها ()getMediaFiles، وهي دالّة سنستخدم فيها أسلوب الاستدعاء الذاتي (Recursive function) للتنقُّل بين المجلّدات الرئيسيّة والفرعيّة والبحث عن الملفّات التي تنتهي بصيغة “mp3.”. ستأخذ الدالّة معاملاً واحداً من نوع File، وهذا المعامل هو فعليّاً عبارة عن عنوان المجلَّد المُراد البحث عن الملفّات بداخله (والذي سيكون عنوان الذاكرة External storage، والذي يُشير إلى “storage/emulated/0/” في معظم أجهزة الآندرويد):

 

المشكلة هي أنَّ لائحة myMedia ستحتوي أسماء الملفّات فقط وليس العنوان كاملاً، وبالتالي لن نتمكَّن من الوصول إلى الملفّ لاحقاً لتشغيله إذا لم نكن نعرف عنوانه، لذلك، سنقوم بتعريف لائحة جديدة نسمّيها “myMediaPaths”، وهي تماماً من نفس نوع myMedia، إلّا أنّها ستحتوي على العنوان كاملاً:

وسنعدِّل على الدالّة ()getMediaFiles بحيث تضيف العناوين كاملة إلى هذه اللائحة، أمّا الأسماء المجرّدة (والتي سنظهرها للمستخدم) ففي لائحة myMedia:

الآن ما علينا سوى استدعاء الدالّة وإلغاء طريقة القراءة من مجلَّد “/res/raw/” التي استخدمناها فيما سبق:

وأخيراً نقوم بالتعديل على معامل دالّة ()setDataSource في ()setOnItemClickListener المستخدمة لتحديد الملفّ المُراد تشغيله في الMediaPlayer ليصبح كالتالي:

إظهار اسم الملفّ قيد التشغيل

يمكننا إضافة TextView لتبيِّن اسم الملفّ قيد التشغيل حاليّاً، فيمكننا تحديد النصّ الظاهر في هذا الTextView عن طريق استخدام ()setText في كلّ مرّة يضغط المستخدم فيها على ملفّ، بحيث نسترجع اسم الملفّ (مجرّداً) من لائحة myMedia ونضع اسمه في الTextView. نقوم بإنشاء TextView جديد في الواجهة، بحيث يكون بين الأزرار وقائمة الصوتيّات:

سنقوم بتسمية عنصر الTextView ب”currentlyPlaying”، بحيث يصبح كود الواجهة كالتالي:

ونقوم بتعريفه برمجيّاً باستخدام ()findViewById كالتالي:

ونقوم بإضافة السطر التالي في دالّة ()onCreate حتّى نتمكَّن من الوصول برمجيّاً إلى عنصر الTextView:

وأخيراً، نقوم بإضافة السطر التالي في دالّة ()mylistview_ref.setOnItemClickListener، بحيث يتم تغيير النصّ في كلّ مرّة يقوم المستخدم باختيار ملفّ صوتي جديد:

اترك تعليق