Xử lý nội dung đa phương tiện
Khóa học lập trình Android cơ bản

Danh sách bài học
Xử lý nội dung đa phương tiện
Dẫn nhập
Ở các bài học trước, chúng ta đã cùng nhau tìm hiểu về HỆ THỐNG PHÂN QUYỀN ỨNG DỤNG nói chung và những thay đổi của hệ thống này từ phiên bản Android 6 Marshmallow.
Ở bài học này, chúng ta tiếp tục tìm hiểu về các loại nội dung đa phương tiện trong Android như hình ảnh, nhạc, video,… và cơ chế ContentProvider mà Android sử dụng để quản lý chúng.
Nội dung
Để đọc hiểu bài này tốt nhất các bạn nên có kiến thức cơ bản về các phần:
- Từng nghe nhạc, xem phim, xem ảnh (cái này thì quá dễ).
- Biết một số định dạng file đa phương tiện phổ biến như MP3, FLAC, JPG, PNG,…
- Đã có thể viết được một ứng dụng Android HelloWorld cơ bản.
Trong bài học này, chúng ta sẽ cùng tìm hiểu các vấn đề:
- Các định dạng đa phương tiện được hỗ trợ trong Android.
- Viết một chương trình nhỏ để xem ảnh, chơi nhạc có sẵn trong máy.
Các định dạng đa phương tiện được hỗ trợ trong Android
Chúng ta đều biết rằng các thông tin đa phương tiện như hình ảnh, âm thanh, video,… được lưu trong máy tính dưới dạng các file. Chúng có thể tồn tại dưới nhiều định dạng khác nhau nữa như nhạc thì có OGG, MP3, FLAC, WAV; ảnh thì phổ biến nhất là JPG và PNG, GIF.
Phần nội dung dưới đây được sơ lược khá nhiều để các bạn có cái nhìn tổng quan là chính. Để tìm hiểu sâu hơn, các bạn có thể đọc cuốn Android Studio New Media Fundamentals của tác giả Wallace Jackson.
Android cũng có hỗ trợ rất nhiều định dạng phổ biến nói trên. Các bạn có thể tham khảo bảng sau để biết sơ qua các định dạng và các đời hệ điều hành hỗ trợ (dấu x là có hỗ trợ):
Cũng khá là dài phải không? Trên thực tế việc sử dụng các API có sẵn của Android để viết ứng dụng chơi nhạc hay xem hình khá là dễ dàng. Chúng ta có thể thử làm với ví dụ ngay sau đây.
Ứng dụng nghe nhạc trong máy
Bước 1: Chúng ta tạo một project mới như thường lệ, lấy tên là MediaExample:
Bước 2: Để ứng dụng có thể mở được nhiều định dạng đa phương tiện thì chúng ta cũng phải cân nhắc mức API. Ở đây mình chọn 4 để hỗ trợ cho nhiều.
- Chọn loại Activity là Empty và tiếp tục:
Bước 3: Để đơn giản hoá toàn bộ kiến thức, chúng ta sẽ thống nhất là viết ứng dụng chơi tất cả những bài nhạc đang có trong máy, hiển thị danh sách bài hát ra ListView. Tạo một model có tên là Song.java:
- Song.java
package com.howkteam.mediaexample;
public class Song {
private long id;
private String title;
private String artist;
public Song(long songID, String songTitle, String songArtist) {
id = songID;
title = songTitle;
artist = songArtist;
}
public long getID() {
return id;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
}
Bước 4: Để hiển thị dạng danh sách thì hiển nhiên là chúng ta sẽ cần đến Adapter và layout cho từng item. Nếu bạn đã quên cách làm thì ngó lại bài LISTVIEW và RECYCLERVIEW trước đó nhé. Đầu tiên chúng ta tạo layout:
- song.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="songPicked"
android:orientation="vertical"
android:padding="5dp">
<TextView
android:id="@+id/song_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#2952a3"
android:textSize="20sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/song_artist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="#3366cc"
android:textSize="18sp"/>
</LinearLayout>
Bước 5: Tạo Adapter cho danh sách bài hát:
- SongAdapter.java
package com.howkteam.mediaexample;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
public class SongAdapter extends BaseAdapter {
// Song list và layout
private ArrayList<Song> songs;
private LayoutInflater songInf;
// Constructor
public SongAdapter(Context c, ArrayList<Song> theSongs) {
songs = theSongs;
songInf = LayoutInflater.from(c);
}
@Override
public int getCount() {
return songs.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Ánh xạ đến layout mỗi bài
LinearLayout songLayout = (LinearLayout) songInf.inflate(R.layout.song, parent, false);
TextView songView = (TextView) songLayout.findViewById(R.id.song_title);
TextView artistView = (TextView) songLayout.findViewById(R.id.song_artist);
// Lấy bài hát hiện
Song currentSong = songs.get(position);
// Lấy tên tiêu đề và tác
songView.setText(currentSong.getTitle());
artistView.setText(currentSong.getArtist());
// Cài đặt tag cho mỗi bài là vị trí của mỗi
songLayout.setTag(position);
return songLayout;
}
}
Bước 6: Để có thể chơi nhạc, chúng ta cần một “Bộ điều khiển” (controller) để phát nhạc. Việc này có thể dễ dàng thực hiện bằng cách tạo lớp kế thừa từ lớp MediaController.
- MusicController.java
package com.howkteam.mediaexample;
import android.content.Context;
import android.widget.MediaController;
public class MusicController extends MediaController {
public MusicController(Context context) {
super(context);
}
}
Bước 7: Hẳn các bạn cũng muốn là sau khi đóng ứng dụng, chương trình phát nhạc vẫn chạy như bình thường đúng không? Rất nhiều ứng dụng nghe nhạc khác cũng làm tương tự, để khi tắt màn hình thì nhạc vẫn chạy, chúng ta có thể làm điều đó bằng cách tạo ra một Service như sau:
- MusicService.java
package com.howkteam.mediaexample;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentUris;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.Random;
public class MusicService extends Service implements
MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
private MediaPlayer player;
private ArrayList<Song> songs;
private int songPos;
private final IBinder musicBind = new MusicBinder();
private String songTitle = "";
private static final int NOTIFY_ID = 1;
private boolean shuffle = false;
private Random rand;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return musicBind;
}
@Override
public void onCreate() {
super.onCreate();
songPos = 0;
// Khởi tạo một bộ phát đa phương tiện mới.
player = new MediaPlayer();
}
public void initMusicPlayer() {
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setOnPreparedListener(this);
player.setOnErrorListener(this);
player.setOnCompletionListener(this);
}
public void setList(ArrayList<Song> theSongs) {
songs = theSongs;
}
public class MusicBinder extends Binder {
MusicService getService() {
return MusicService.this;
}
}
@Override
public void onCompletion(MediaPlayer mp) {
if (player.getCurrentPosition() > 0) {
mp.reset();
playNext();
}
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();
return false;
}
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT
);
Notification.Builder nBuilder = new Notification.Builder(this);
nBuilder.setContentIntent(pendingIntent)
.setTicker(songTitle)
.setSmallIcon(R.drawable.play)
.setOngoing(true)
.setContentTitle("Playing")
.setContentText(songTitle);
Notification notif = nBuilder.getNotification();
startForeground(NOTIFY_ID, notif);
}
@Override
public boolean onUnbind(Intent intent) {
player.stop();
player.release();
return false;
}
public void playSong() {
player.reset();
Song playSong = songs.get(songPos);
songTitle = playSong.getTitle();
Long currentSong = playSong.getID();
Uri trackUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, currentSong);
try {
player.setDataSource(getApplicationContext(), trackUri);
} catch (Exception e) {
Log.e("MUSIC SERVICE", "Error starting data source", e);
}
player.prepareAsync();
}
public void setSong(int songIndex) {
songPos = songIndex;
}
public int getSongPos() {
return player.getCurrentPosition();
}
public int getDur() {
return player.getDuration();
}
public boolean isPlaying() {
return player.isPlaying();
}
public void pausePlayer() {
player.pause();
}
public void seek(int pos) {
player.seekTo(pos);
}
public void go() {
player.start();
}
public void playPrev() {
songPos--;
if (songPos < 0) songPos = songs.size() - 1;
playSong();
}
public void playNext() {
if (shuffle) {
int newSongPos = songPos;
while (newSongPos == songPos) {
newSongPos = rand.nextInt(songs.size());
}
songPos = newSongPos;
} else {
songPos++;
if (songPos >= songs.size()) songPos = 0;
}
playSong();
}
@Override
public void onDestroy() {
stopForeground(true);
}
public void setShuffle() {
if (shuffle) shuffle = false;
else shuffle = true;
}
}
Bước 8: Chỉnh sửa file activity_main.xml để thêm ListView vào:
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:background="#ffebe5"
tools:context=".MainActivity">
<ListView
android:id="@+id/song_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Và cuối cùng, chỉnh sửa file MainActivity.java để gắn kết các Service, Controller với nhau:
- MainActivity.java
package com.howkteam.mediaexample;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.MediaController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class MainActivity extends AppCompatActivity implements MediaController.MediaPlayerControl {
private ArrayList<Song> songList;
private ListView songView;
private MusicService musicService;
private Intent playIntent;
private boolean musicBound = false;
private MusicController controller;
private boolean paused = false;
private boolean playbackPaused = false;
@Override
protected void onStart() {
super.onStart();
if (playIntent == null) {
playIntent = new Intent(this, MusicService.class);
bindService(playIntent, musicConnection, BIND_AUTO_CREATE);
startService(playIntent);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Retrieve list menu
songView = (ListView) findViewById(R.id.song_list);
// Instantiate song list
songList = new ArrayList<Song>();
// Get songs from device
getSongList();
// Sort alphabetically by title
Collections.sort(songList, new Comparator<Song>() {
@Override
public int compare(Song lhs, Song rhs) {
return lhs.getTitle().compareTo(rhs.getTitle());
}
});
// Create and set adapter
SongAdapter songAdapter = new SongAdapter(this, songList);
songView.setAdapter(songAdapter);
setController();
}
// Method to retrieve song infos from device
public void getSongList() {
// Query external audio resources
ContentResolver musicResolver = getContentResolver();
Uri musicUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicUri, null, null, null, null);
// Iterate over results if valid
if (musicCursor != null && musicCursor.moveToFirst()) {
// Get columns
int titleColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
int idColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media._ID);
int artistColumn = musicCursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
do {
Long thisId = musicCursor.getLong(idColumn);
String thisTitle = musicCursor.getString(titleColumn);
String thisArtist = musicCursor.getString(artistColumn);
songList.add(new Song(thisId, thisTitle, thisArtist));
}
while (musicCursor.moveToNext());
}
}
// Connect with the service
private ServiceConnection musicConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
musicService.setList(songList);
musicBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
musicBound = false;
}
};
public void songPicked(View v) {
musicService.setSong(Integer.parseInt(v.getTag().toString()));
musicService.playSong();
if (playbackPaused) {
setController();
playbackPaused = false;
}
controller.show(0);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.menu_main, menu);
return true;
//return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_shuffle:
musicService.setShuffle();
break;
case R.id.action_end:
stopService(playIntent);
musicService = null;
System.exit(0);
break;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
stopService(playIntent);
musicService = null;
super.onDestroy();
}
private void setController() {
controller = new MusicController(this);
controller.setPrevNextListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
playNext();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
playPrev();
}
});
controller.setMediaPlayer(this);
controller.setAnchorView(findViewById(R.id.song_list));
controller.setEnabled(true);
}
private void playNext() {
musicService.playNext();
if (playbackPaused) {
setController();
playbackPaused = false;
}
controller.show(0);
}
private void playPrev() {
musicService.playPrev();
if (playbackPaused) {
setController();
playbackPaused = false;
}
controller.show(0);
}
@Override
public boolean canPause() {
return true;
}
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
@Override
protected void onPause() {
super.onPause();
paused = true;
}
@Override
protected void onResume() {
super.onResume();
if (paused) {
setController();
paused = false;
}
}
@Override
protected void onStop() {
controller.hide();
super.onStop();
}
@Override
public void start() {
musicService.go();
}
@Override
public void pause() {
playbackPaused = true;
musicService.pausePlayer();
}
@Override
public int getDuration() {
if (musicService != null && musicBound && musicService.isPlaying()) {
return musicService.getDur();
}
return 0;
}
@Override
public int getCurrentPosition() {
return 0;
}
@Override
public void seekTo(int pos) {
musicService.seek(pos);
}
@Override
public boolean isPlaying() {
if (musicService != null && musicBound) {
return musicService.isPlaying();
} else {
return false;
}
}
@Override
public int getBufferPercentage() {
return 0;
}
@Override
public int getAudioSessionId() {
return 0;
}
}
Bước 9: Do app sử dụng menu và một số thành phần drawable ngoài, các bạn có thể tìm thấy trong thư mục /res/drawable ở file project đính kèm. Ngoài ra các bạn cần tạo thêm menu cho nó bằng cách chuột phải vào thư mục /res > New > Android resource directory
- Tạo folder với thông số như hình:
Và tạo bên trong một file menu_main.xml với nội dung như sau:
<menu 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"
tools:context=".MainActivity">
<item
android:id="@+id/action_shuffle"
android:icon="@drawable/rand"
android:orderInCategory="1"
android:title="Shuffle"
app:showAsAction="always"/>
<item
android:id="@+id/action_end"
android:icon="@drawable/end"
android:orderInCategory="2"
android:title="End"
app:showAsAction="always"/>
</menu>
Bước 10: Đừng quên khai báo Permission trong file AndroidManifest, nếu không ứng dụng sẽ bị crash!
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.howkteam.mediaexample">
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name="com.howkteam.mediaexample.MusicService"/>
</application>
</manifest>
Thêm nữa, chúng ta sẽ chỉnh file build.gradle cho Target dưới 23, ở đây mình chọn 22, để tạm thời tránh xung đột với Runtime Permission trên Android 6 trở lên (phần targetSdkVersion):
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
defaultConfig {
applicationId "com.howkteam.mediaexample"
minSdkVersion 14
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
testCompile 'junit:junit:4.12'
}
Nào, chạy app, và chúng ta có được:
Chọn bài và nhấn nút Play nhé ^_^
Kết luận
Qua bài này chúng ta đã nắm được Các định dạng đa phương tiện cơ bản trong Android, và sử dụng Service để làm một ứng dụng chơi nhạc nho nhỏ, thuận tiện.
Bài sau chúng ta sẽ tìm hiểu về CÁCH LƯU TRỮ DỮ LIỆU DẠNG KEY - VALUE TRONG ANDROID.
Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.
Tải xuống
Tài liệu
Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học Xử lý nội dung đa phương tiện dưới dạng file PDF trong link bên dưới.
Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com
Đừng quên like và share để ủng hộ Kteam và tác giả nhé!

Thảo luận
Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.
Nội dung bài viết
Tác giả/Dịch giả
Khóa học
Khóa học lập trình Android cơ bản
Serial tutorial hướng dẫn lập trình Android cơ bản
Project hoàn chỉnh cho bạn nào cần: Link Project