É muito comum fazermos uso de recursos multimídia em nossas aplicações, sejam eles imagens, vídeos ou sons. Neste artigo apresentaremos como lidar com esses diferentes tipos de mídia na plataforma Android através do desenvolvimento de três estudos de caso. Com isso, você terá total condições de, ao final da leitura, trabalhar com estes recursos em suas aplicações móveis.
Conceitualmente, uma mídia é um canal ou ferramenta usada para armazenamento e transmissão de informação ou dado. Existem diversos tipos de mídia, entre eles: texto, imagem, áudio e vídeo.
Guia do artigo:
Já a multimídia é a junção de duas ou mais mídias ao mesmo tempo. Ela é controlada por computador fazendo a integração de mídias, como as citadas acima, e qualquer outro meio onde todo tipo de informação pode ser representado, armazenada, transmitida e processada digitalmente.
As principais características da multimídia são:
- Interatividade: o usuário pode participar da atividade;
- Acesso não-linear: o usuário não precisa acessar as informações em uma sequência predefinida.
Este artigo apresenta o uso de três tipos específicos de mídias em aplicações Android: imagens, sons e vídeos, permitindo ao leitor, ao final da leitura, ler e reproduzir a maioria destas mídias.
Utilização de recursos multimídias a partir de aplicações Android
O controle de reprodução de arquivos de áudio/vídeo é realizado de forma linear, já que a execução dessas mídias deve respeitar uma estrutura. Assim, pode-se dizer que essa reprodução deve respeitar alguns estados. A Figura 1 mostra o ciclo de vida e os estados do objeto MediaPlayer, responsável pelos controles e reprodução de áudio/vídeo.
Quando um objeto MediaPlayer é criado ou após o seu método reset() ser chamado, o objeto fica ocioso, ou seja, no estado Idle. Nesse estado, se o objeto não for mais utilizado, recomenda-se chamar o método release(), o qual tem a função de encerrar a reprodução da mídia, liberando recursos e colocando o MediaPlayer em estado End.
O passo seguinte é colocar o objeto MediaPlayer no estado Prepared, o qual torna o objeto apto a executar uma mídia. Há duas maneiras (síncrona ou assíncrona) de fazer isso: realizar uma chamada ao método prepare() do MediaPlayer (modo síncrono), o qual transfere automaticamente o estado do objeto para Prepared, ou chamar o métodoprepareAsync()(modo assíncrono). No último, o objeto MediaPlayer é transferido para o estado Prepared quase que de forma imediata, mas em paralelo a isso, o recurso que será executado está sendo preparado e, quando finalizado, chama o método onPrepared() da interface OnPreparedListener.
Após preparada a mídia, sua execução pode ser iniciada a partir do método start(), o qual coloca o objeto em modo de reprodução, ou seja, o método isPalying() do objeto MediaPlayer retornará um booleano true.
Tanto nos estados de Prepared, Started ou Paused, a reprodução de uma mídia pode ser interrompida através do método stop(), o qual coloca o objeto MediaPlayer em estado de Stopped. Isto fará com que a reprodução da mídia seja interrompida.
Uma boa prática é, ao longo da execução de um vídeo, principalmente se a reprodução for assíncrona (já que muitas vezes essa depende da rede Internet), realizar a captura e o tratamento de exceções, tais como a IllegalStateException, a qual é lançada quando um determinado método é chamado de fora de um estado permitido. Exceções como IllegalArgumentException também podem ser geradas ao passar um parâmetro incompatível com o método setDataSource(), por exemplo, assim como o IOException pode ser gerado a qualquer momento durante a reprodução.
Estudos de Caso
Este artigo apresenta três aplicativos: um player de áudio com possibilidade de selecionar um áudio, um aplicativo para captura de imagens através da câmera do smartphone e um aplicativo para captura de vídeo também através da câmera do smartphone. Para os testes utilizamos um Galaxy SIII Mini.
Desenvolvendo um Player de Áudio
O aplicativo de player de áudio possui a funcionalidade de escolha de um áudio para execução. A classe MediaPlayer é capaz de, em tempo de execução, identificar o formato do áudio (wma, mp3, entre outros) não sendo necessário personalizações durante a codificação.
O player desenvolvido também conta com recursos como botão de Play (iniciar a reprodução do áudio), pause (pausar a reprodução, permitindo que o usuário continue do ponto que parou através do botão play) e stop, o qual para a reprodução, sendo que nesta situação, ao clicar em play, o áudio é reproduzido do início novamente.
Na parte superior da tela, são apresentados dois campos de texto, o primeiro, com o tempo percorrido desde o início da reprodução do áudio, e o segundo, com o tempo total do áudio. A interface visual do aplicativo é apresentada na Figura 2.
Para o desenvolvimento desse aplicativo, o primeiro passo foi codificar a interface gráfica no arquivo activity_principal.xml, conforme código apresentado na Listagem 1.
01. <LinearLayout
02. xmlns:android="http://schemas.android.com/apk/res/android"
03. xmlns:tools="http://schemas.android.com/tools"
04. android:layout_width="match_parent"
05. android:layout_height="match_parent"
06. tools:context=".PrincipalActivity"
07. android:orientation="vertical">
08.
09. <View
10. android:layout_width="match_parent"
11. android:layout_height="1dp"
12. android:layout_marginTop="5dp"
13. android:layout_marginBottom="5dp"/>
15.
16. <TextView
17. android:id="@+id/tvTime"
18. android:layout_width="fill_parent"
19. android:layout_height="wrap_content"
20. android:gravity="center"
21. android:text="00:00 / 00:00" />
22.
23. <View
24. android:layout_width="match_parent"
25. android:layout_height="1dp"
26. android:layout_marginTop="5dp"
27. android:layout_marginBottom="5dp"/>
29.
30. <LinearLayout
31. android:layout_width="match_parent"
32. android:layout_height="wrap_content"
33. android:orientation="horizontal" >
34.
35. <Button
36. android:layout_width="0dp"
37. android:layout_height="wrap_content"
38. android:layout_weight="1"
39. android:text="Play"
40. android:onClick="playMusicOnClick" />
41.
42. <Button
43. android:layout_width="0dp"
44. android:layout_height="wrap_content"
45. android:layout_weight="1"
46. android:layout_marginRight="5dp"
47. android:layout_marginLeft="5dp"
48. android:text="Pause"
49. android:onClick="pauseMusicOnClick" />
50.
51. <Button
52. android:layout_width="0dp"
53. android:layout_height="wrap_content"
54. android:layout_weight="1"
55. android:text="Stop"
56. android:onClick="stopMusicOnClick" />
57.
58. </LinearLayout>
59.
60. <View
61. android:layout_width="match_parent"
62. android:layout_height="1dp"
63. android:layout_marginTop="5dp"
64. android:layout_marginBottom="5dp"
65. android:background="#ddd" />
66.
67.
68. <LinearLayout
69. android:layout_width="match_parent"
70. android:layout_height="wrap_content"
71. android:orientation="horizontal" >
72.
73. <Button
74. android:layout_width="0dp"
75. android:layout_height="wrap_content"
76. android:layout_weight="1"
77. android:text="Biblioteca"
78. android:onClick="pegarMusicOnClick" />
79.
80. </LinearLayout>
81.
82. </LinearLayout>
A interface conta com seis componentes visuais. Eles foram adicionados em um layout LinearLayout (linha 01), definido no formato vertical (linha 07). Sendo assim, um componente será adicionado abaixo do outro na interface.
São utilizados três componentes Views (linhas 09, 23 e 60) na interface gráfica para serem utilizados como separadores entre componentes. Além disso, é utilizado um componente TextView (linha 16), sendo apresentado nele o tempo atual da reprodução e o tempo total do áudio.
Para uma melhor organização do layout, foi adicionado um LinearLayout (linha 30) definido no formato horizontal para que os componentes de controle de reprodução sejam adicionados um ao lado do outro na interface. Dentro desse layout foram adicionados três componentes Button (linhas 35, 42 e 51), sendo o primeiro para a ação de iniciar o áudio, o segundo para pausar o áudio e o terceiro para interromper (linhas 40, 49 e 56).
Depois disso, foi adicionado outro LinearLayout (linha 68) definido no formato horizontal para comportar o componente Button (linha 73) de escolha do áudio pelo usuário.
O próximo passo, após codificar a interface gráfica, é tratá-la a partir do código Java. Para isso, a classe PrincipalActivity.java deve ser codificada conforme Listagens 2, 3 e 4.
01. package br.edu.utfpr.playeraudio;
02.
03. import java.io.File;
04. import java.io.IOException;
05.
06. import android.app.Activity;
07. import android.content.Intent;
08. import android.database.Cursor;
09. import android.media.AudioManager;
10. import android.media.MediaPlayer;
11. import android.media.MediaPlayer.OnBufferingUpdateListener;
12. import android.media.MediaPlayer.OnCompletionListener;
13. import android.media.MediaPlayer.OnErrorListener;
14. import android.media.MediaPlayer.OnPreparedListener;
15. import android.media.MediaPlayer.OnSeekCompleteListener;
16. import android.net.Uri;
17. import android.os.Bundle;
18. import android.provider.MediaStore;
19. import android.util.Log;
20. import android.view.View;
21. import android.widget.TextView;
22.
23. public class PrincipalActivity extends Activity implements OnPreparedListener,
OnSeekCompleteListener, OnCompletionListener, OnErrorListener {
24.
25.
26.
27. private MediaPlayer player;
28. private TextView tvTime;
29. private long currentTime;
29. private long duration;
30. private boolean isPlaying;
31. public static final int MUSICA_INTERNA = 1;
32. private String pathMusic;
33.
34. @Override
35. protected void onCreate(Bundle savedInstanceState) {
36. super.onCreate(savedInstanceState);
37. setContentView(R.layout.activity_principal);
38.
39. tvTime = (TextView) findViewById(R.id.tvTime);
40.
41. if(savedInstanceState != null){
42. duration = savedInstanceState.getLong("duration");
43. currentTime = savedInstanceState.getLong("currentTime");
44. isPlaying = savedInstanceState.getBoolean("isPlaying");
45.
46. if(isPlaying){
47. try {
48. playMusicOnClick(null);
49. } catch (IOException e) {
50. e.printStackTrace();
51. }
52. }
53.
54. }
55.
56. }
57.
58.
59. public void pegarMusicOnClick(View view){
60. stopMusicOnClick(null);
61.
62. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
63. intent.setType("audio/*");
64. startActivityForResult(intent, MUSICA_INTERNA);
65. }
66.
67. @Override
68. protected void onActivityResult(int requestCode, int resultCode,Intent intent){
69.
70. if (requestCode == MUSICA_INTERNA && resultCode == RESULT_OK){
71. Uri musicaSelecionada = intent.getData();
72.
73. String[] colunas = {MediaStore.Audio.Media.DATA};
74.
75. Cursor cursor = getContentResolver().query(musicaSelecionada, colunas, null, null, null);
76.
77. cursor.moveToFirst();
78.
79. int indexColuna = cursor.getColumnIndex(colunas[0]);
80. pathMusic = cursor.getString(indexColuna);
81. cursor.close();
82.
83. try {
84. playMusicOnClick(null);
85. } catch (IOException e) {
86. // TODO Auto-generated catch block
87. e.printStackTrace();
88. }
89.
90. }
91.
92. }
Na linha 23 é declarada a única Activity do aplicativo, a qual implementa as interfaces para manipulação dos dados multimídias.
Na linha 27 é declarado o objeto MediaPlayer, que é responsável pela execução do áudio. Já o componente visual tvTime serve para apresentação do tempo do áudio. Temos também variáveis de controle, como a currentTime, que armazenará a posição atual do áudio, duration, que armazena o tempo do áudio, isPlaying, que armazena um booleano com a situação da execução, e um texto pathMusic com o caminho do áudio.
O método onCreate() é chamado quando o aplicativo é iniciado. Ele inicia a Activity (linha 36), apresenta a interface gráfica para o usuário (linha 37) e recupera o componente visual para apresentação do tempo do áudio (linha 39).
Na linha 41 é verificado se o aplicativo foi iniciado ou veio do estado de pausa/parado (que ocorre quando, por exemplo, durante a execução de um áudio o smartphone recebe uma ligação). Caso tenha vindo do estado de pausa/parado, o objeto savedInstanceState retorna diferente de null, recuperando a duração do áudio, tempo de execução e se estava sendo executado no momento da pausa/parada (linhas 42 a 44).
Se o áudio estava em execução (linha 46), então o código correspondente ao clique do botão play é executado (linha 48). Em um início normal, ou seja, quando não é originado de um estado de pausado/parado, a interface é apresentada para o usuário, o qual deve clicar no botão Biblioteca para buscar um áudio para execução.
Quando o usuário clica no botão, o método pegarMusicOnClick (linha 59) é executado. Caso algum áudio esteja em execução, o mesmo é encerrado (linha 60), executando o método correspondente ao botão stop da tela.
Após isso, um Intent com a função de apresentar a tela de seleção do áudio é instanciado (linha 62), sendo filtrado por conteúdo de áudio (linha 63). Por fim, a tela de seleção de áudio é apresentada ao usuário (ver Figura 3).
A constante MUSICA_INTERNA (linha 31) é um valor utilizado para identificar uma chamada de Activity por meio do startActivityForResult (utilizado na linha 64). Quando se chama uma Activity com esse método, o método onActivityResult() é tratado no seu retorno – linha 68, o qual faz o teste pela constante MUSICA_INTERNA (linha 70) para verificar se ele é referente à chamada realizada.
Assim, após a identificação de retorno no método onActivityResult(), sendo identificado que ele é referente à chamada realizada na linha 64, é recebido o URI (Uniform Resource Identifier) do áudio selecionado (linha 71). Essa URI traz um conjunto de informações referentes ao local onde um determinado recurso está localizado.
URI nada mais é do que uma identificação interna de um recurso. Assim como temos na Internet as URLs, que permitem identificar caminhos de páginas, na programação temos as URIs, que possuem caminhos e dados originados de recursos internos do próprio dispositivos, ficando transparente ao programador onde estão e como são recuperados esses dados.
Para fazer uma consulta no URI, é necessária uma lista de campos. Esta lista representa basicamente quais as informações que serão recuperadas a partir da consulta. Neste exemplo, esses campos são definidos pelo array de String da linha 73, o qual apresenta que será recuperado o campo MediaStore.Audio.Media.DATA, sendo esse o caminho do áudio.
Em seguida é realizada uma pesquisa na URI para recuperar o caminho do áudio. Para isso, um objeto cursor foi instanciado (linha 75) para armazenar o resultado da pesquisa.
O objeto Cursor é bastante conhecido por programadores que usam o armazenamento de dados no SQLite, pois em um comando SELECT, por exemplo, o resultado da pesquisa também fica armazenado em um objeto Cursor. O Cursor permite armazenar um conjunto de informações como se fosse uma matriz bidimensional formada por linhas (registros da consulta) e as colunas (dados recuperados).
A linha 77 posiciona o cursor no primeiro resultado da pesquisa, a linha 79 recupera o índice do campo que possui o caminho do arquivo de áudio e, por fim, a linha 80 recupera o caminho do áudio e a 81 encerra o cursor.
Confira também
Após recuperado o caminho do arquivo, o método playMusicOnClick (linha 84) é executado. Vale notar que esse é o mesmo método executado quando o usuário clica no botão play.
Já na Listagem 3, o método onSaveInstanceState (linha 95) é chamado antes da Activity entrar em estado de pausa ou parado. Isso é necessário para que seja possível parar o áudio quando, por exemplo, chegar uma ligação. Esse método tem a função de armazenar no objeto output duas informações do tipo long e um booleano: a duração do áudio, o tempo de execução do áudio e se o áudio está em execução (linhas 98 a 100).
93.
94. @Override
95. public void onSaveInstanceState (Bundle output){
96. super.onSaveInstanceState(output);
97.
98. output.putLong("duration", duration);
99. output.putLong("currentTime", currentTime);
100. output.putBoolean("isPlaying", isPlaying);
101.
102. }
103.
104.
105. @Override
106. public void onPause(){
107. super.onPause();
108.
109. if(player != null){
110. duration = player.getDuration();
111. urrentTime = player.getCurrentPosition();
112. }
113. }
114.
115.
116. @Override
117. public void onDestroy(){
118. super.onDestroy();
119. if(player != null){
120. player.stop();
121. player.release();
122. player = null;
123. }
124. }
125.
126. public void playMusicOnClick(View view) throws IOException{
127. if(player == null){
128. try {
129.
130. if(pathMusic != null){
131. player = new MediaPlayer();
132.
133. File file = new File(pathMusic);
134.
135. player.setDataSource(file.getAbsolutePath().toString());
136. player.prepare();
137. }
138.
139. player.setOnBufferingUpdateListener(this);
140. player.setOnCompletionListener(this);
141. player.setOnErrorListener(this);
142. player.setOnPreparedListener(this);
143. player.setOnSeekCompleteListener(this);
144.
145. } catch (IllegalArgumentException e) {e.printStackTrace();}
146. catch (SecurityException e) {e.printStackTrace();}
147. catch (IllegalStateException e) {e.printStackTrace();}
148.
149. }else{
150. player.start();
151. isPlaying = true;
152. updateTimeMusicThread(player, tvTime);
153. }
154. }
155.
156. public void pauseMusicOnClick(View view){
157. isPlaying = false;
158. if(player != null){
159. player.pause();
160. }
161. }
162.
163. public void stopMusicOnClick(View view){
164. isPlaying = false;
165. if(player != null){
166. player.stop();
167. player.release();
168. player = null;
169. urrentTime = 0;
170. tvTime.setText("00:00 / 00:00");
171.
172. }
173. }
Antes de entrar em estado de pausa ou parado, o método onPause() é executado (linha 106) para verificar se o player já foi instanciado. Caso positivo, é armazenado na variável a duração e o tempo atual do áudio (linhas 110 e 111), os quais serão utilizados na persistência no método onSaveInstanceState().
Quando a aplicação é encerrada, é necessário finalizar a execução do áudio. Para isso, o método onDetroy() é chamado (linha 117). Ele verifica se um áudio foi executado, encerra sua execução e libera o recurso definindo como nulo o objeto player (linhas 119 a 123).
O método playMusicOnClick (linha 126) tem a função de reproduzir o áudio, realizando uma verificação para identificar se o objeto player já não está instanciado (linha 127). Se não estiver, é verificado se existe o caminho do áudio selecionado, instanciado o objeto player e criando um objeto File, o qual é passado por parâmetro no método setDataSource (linhas 130 a 135). Por fim, o áudio é preparado para execução de forma síncrona (linha 136) e são definidos alguns listeners para acompanhar a execução do áudio (linhas 139 a 143).
O else da linha 149 corresponde ao fato do componente player já ter sido instanciado anteriormente. Nessa situação, a execução do áudio é iniciada (linha 150) e o estado da variável isPlaying é alterado para true.
Com a execução do áudio, uma thread deve ser iniciada para atualizar o tempo de execução. Isso é feito no método updateTimeMusicThread (linha 152) (que será implementado na próxima listagem). O método pauseMusicOnClick é chamado quando o usuário clica no botão pause da tela, valorizando a variável isPlaying para false, assim como executando no player o método pause (linhas 156 a 161).
Já o método stopMusicOnClick (linha 163) é chamado quando o usuário clica no botão stop da tela, o qual também valoriza a variável isPlaying para false, para a execução do áudio, assim como libera o objeto player (linhas 164 e 168). Em seguida, o tempo atual de execução e o do componente visual são reiniciados (linhas 169 e 170).
Na Listagem 4, os métodos de listener onCompletion (linha 176), onPrepared (linha 182), onSeekComplete (linha 240), onError (linha 245) e onBufferingUpdate (linha 251) são utilizados para apresentar as mensagens referentes à execução do programa no LogCat, conforme pode ser observado na Figura 4.
174.
175. @Override
176. public void onCompletion(MediaPlayer mp) {
177. Log.i("Script", "onCompletion()");
178.
179. }
180.
181. @Override
182. public void onPrepared(MediaPlayer mp) {
183. Log.i("Script", "onPrepared()");
184.
185. isPlaying = true;
186.
187. mp.start();
188. mp.setLooping(false);
189.
190. mp.seekTo((int)currentTime);
191.
192. updateTimeMusicThread(mp, tvTime);
193. }
194.
195. public void updateTimeMusicThread(final long duration,
final long currentTime, final TextView view){
196.
197. runOnUiThread(new Runnable() {
198. public void run() {
199. long aux;
200. int minute, second;
201.
202.
203. //Duration
204. aux = duration / 1000;
205. minute = (int) (aux / 60);
206. second = (int) (aux % 60);
207. String sDuration = minute < 10 ? "0"+minute : minute+"";
208. sDuration += ":"+(second < 10 ? "0"+second : second);
209.
210. //CurrentTime
211. aux = currentTime / 1000;
212. minute = (int) (aux / 60);
213. second = (int) (aux % 60);
214. String sCurrentTime = minute < 10 ? "0"+minute : minute+"";
215. sCurrentTime += ":"+(second < 10 ? "0"+second : second);
216.
217.
218. view.setText(sCurrentTime +" / " +sDuration);
219. }
220. });
221. }
222.
223. public void updateTimeMusicThread(final MediaPlayer mp, final TextView view){
224. new Thread(){
225. public void run(){
226. while(isPlaying){
227. try{
228. updateTimeMusicThread(mp.getDuration(), mp.getCurrentPosition(), view);
229.
230. Thread.sleep(1000);
231. }catch(IllegalStateException e){e.printStackTrace();}
232. catch (InterruptedException e) {e.printStackTrace();
233. }
234. }
235. }
236. }.start();
237. }
238.
239. @Override
240. public void onSeekComplete(MediaPlayer mp) {
241. Log.i("Script", "onSeekComplete()");
242. }
243.
244. @Override
245. public boolean onError(MediaPlayer mp, int what, int extra) {
246. Log.i("Script", "onError()");
247. return false;
248. }
249.
250. @Override
251. public void onBufferingUpdate(MediaPlayer mp, int percent) {
252. Log.i("Script", "onBufferingUpdate()");
253. }
254.}
Dos métodos listeners, o único que tem uma função bem específica no programa é o onPrepared (linha 182), sendo chamado quando o áudio está pronto para a execução (após o método prepare() executado na linha 136). Esse método muda a variável isPlaying para true, assim como inicia a execução do áudio, define que a execução não será em looping e posiciona o áudio para a posição definida pela variável currentTime (linhas 185 a 190). Por fim, uma thread para a atualização do tempo na tela é iniciada (linha 192).
Essa thread é executada na linha 223 e tem a função de, enquanto o áudio estiver em reprodução (linha 226), atualizar a informação referente ao tempo de execução do áudio (linha 228) uma vez por segundo (linha 230).
Para a apresentação dos dados, o método updateTimeMusicThread (codificado na linha 195) é chamado. Ele converte as informações de milissegundos para minutos e segundos, apresentando-as na tela (linha 218).
Para testar este aplicativo foi utilizado um dispositivo Android real. A tela do aplicativo em execução é apresentada na Figura 5.
Como pode ser observado, após iniciar o aplicativo, quando o usuário abre a galeria de áudio, é solicitado que seja indicado por qual programa se deseja acessar o arquivo, para só então ser apresentada a lista de áudios. Ao selecionar um arquivo e confirmar, a aplicação retorna para a tela principal já com o áudio em execução.
Além da forma apresentada, na qual o usuário seleciona um áudio na galeria do device, outras maneiras de execução de áudio podem ser realizadas: executar um áudio presente na pasta do projeto ou um áudio presente na internet. Para isso, basta modificar o método playMusicOnClick (linha 126 da Listagem 3) para o código da Listagem 5 ou da Listagem 6.
01. public void playMusicOnClick(View view) throws IOException{
02. if(player == null){
03. try {
04.
05. player = MediaPlayer.create(this, R.raw.music);
06.
07. player.setOnBufferingUpdateListener(this);
08. player.setOnCompletionListener(this);
09. player.setOnErrorListener(this);
10. player.setOnPreparedListener(this);
11. player.setOnSeekCompleteListener(this);
12.
13. } catch (IllegalArgumentException e) {e.printStackTrace();}
14. catch (SecurityException e) {e.printStackTrace();}
15. catch (IllegalStateException e) {e.printStackTrace();}
16.
17. }else{
18. player.start();
19. isPlaying = true;
20. updateTimeMusicThread(player, tvTime);
21. }
22. }
Nesse exemplo, um áudio com o nome de music.mp3 foi copiado para a pasta do projeto diretamente na pasta res, subpasta raw, sendo referenciado via código fonte pelo R.raw.music (linha 05). Como observado, basta instanciar um objeto do tipo MediaPlayer passando o áudio como parâmetro (linha 05) e a sequência do código permanece a mesma da Listagem 3.
Já o código da Listagem 6 define que o tipo de áudio que será executado é streaming (linha 05) e valoriza o caminho de streaming diretamente da Internet (linha 08) utilizando a URL fictícia: “http://seudominio.com/music.mp3”. Note que ela deverá ser substituída por uma funcional. Lembre-se de adicionar a permissão no arquivo de manifesto para acessar a internet. Depois disso, o áudio é preparado para ser executado de forma assíncrona (linha 08). A partir desse ponto, o restante do código permanece o mesmo dos demais apresentados até o momento.
01. public void playMusicOnClick(View view) throws IOException{
02. if(player == null){
03. try {
04. player = new MediaPlayer();
05. player.setAudioStreamType(AudioManager.STREAM_MUSIC);
06.
07. player.setDataSource("http://seudominio.com/music.mp3");
08. player.prepareAsync();
09.
10. player.setOnBufferingUpdateListener(this);
11. player.setOnCompletionListener(this);
12. player.setOnErrorListener(this);
13. player.setOnPreparedListener(this);
14. player.setOnSeekCompleteListener(this);
15.
16. } catch (IllegalArgumentException e) {e.printStackTrace();}
17. catch (SecurityException e) {e.printStackTrace();}
18. catch (IllegalStateException e) {e.printStackTrace();}
19. catch (Exception e) {e.printStackTrace();}
20. }else{
21. player.start();
22. isPlaying = true;
23. updateTimeMusicThread(player, tvTime);
24. }
25. }
Desenvolvendo um aplicativo para captura de imagem
Para apresentar um exemplo de captura de imagem a partir da câmera do celular, um novo aplicativo foi desenvolvido contendo uma interface gráfica e uma classe.
O exemplo apresentado se resume a uma tela com um componente ImageView, no qual o usuário poderá visualizar a foto capturada a partir da câmera do celular. Essa foto, além de apresentada para o usuário, também será armazenada na pasta de fotos do device Android.
Na parte inferior da tela, um botão responsável pela captura da foto foi adicionado. Ao clicar no botão, a tela padrão de captura do device Android é apresentada com todos os seus recursos (foco, zoom, flash, efeitos, etc.). Após a captura, é apresentada uma tela de confirmação, para só então apresentar a foto para o usuário e armazená-la no device.
O código referente à interface gráfica do aplicativo é apresentado na Listagem 7.
01. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. android:orientation="vertical"
06. tools:context=".PrincipalActivity" >
07.
08. <View
09. android:layout_width="match_parent"
10. android:layout_height="1dp"
11. android:layout_marginBottom="5dp"
12. android:layout_marginTop="5dp"
13. android:background="#ddd" />
14.
15. <TextView
16. android:layout_width="fill_parent"
17. android:layout_height="wrap_content"
18. android:gravity="center"
19. android:text="Foto" />
20.
21. <View
23. android:layout_width="match_parent"
24. android:layout_height="1dp"
25. android:layout_marginBottom="5dp"
26. android:layout_marginTop="5dp"
27. android:background="#ddd" />
28.
29. <ImageView
30. android:id="@+id/imageView"
31. android:layout_width="match_parent"
32. android:layout_height="200dp"
33. android:layout_weight="8" />
34.
35. <LinearLayout
36. android:layout_width="match_parent"
37. android:layout_height="wrap_content"
38. android:orientation="horizontal" >
39.
40. <Button
41. android:layout_width="match_parent"
42. android:layout_height="wrap_content"
43. android:layout_weight="1"
44. android:onClick="tirarFotoOnClick"
45. android:text="Tirar Foto" />
46. </LinearLayout>
47. </LinearLayout>
A interface conta com cinco componentes visuais que foram adicionados em um layout linear (linha 01), o qual foi definido no formato vertical (linha 05).
São utilizados dois componentes View (linha 08 e 21), que servem como separadores entre componentes. Temos também um TextView (linha 15), onde será apresentada a palavra “Foto” para indicar que o usuário está capturando uma imagem. Além disso, foi inserido um componente ImageView para a exibição da imagem capturada (linha 29) e um componente Button (linha 40) responsável por iniciar a captura de imagem.
Para o tratamento da interface, foi codificada a PrincipalActivity.java, sendo o código apresentado na Listagem 8.
01. package br.edu.utfpr.usandocamerafoto;
02.
03. import java.io.File;
04. import java.io.FileInputStream;
05. import java.io.FileNotFoundException;
06. import java.io.IOException;
07. import java.util.Date;
08. import java.text.SimpleDateFormat;
09.
10. import android.app.Activity;
11. import android.content.Intent;
12. import android.graphics.Bitmap;
13. import android.graphics.BitmapFactory;
14. import android.net.Uri;
15. import android.os.Bundle;
16. import android.os.Environment;
18. import android.provider.MediaStore;
19. import android.view.View;
20. import android.widget.ImageView;
21.
22. public class PrincipalActivity extends Activity {
23.
24. private File imageFile;
25. private ImageView imageView;
26. private static final int REQUEST_PICTURE = 1000;
27.
28. @Override
29. protected void onCreate(Bundle savedInstanceState) {
30. super.onCreate(savedInstanceState);
31. setContentView(R.layout.activity_principal);
32.
33. this.imageView = (ImageView) findViewById(R.id.imageView);
34.
35. File fotoDir = Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_PICTURES);
36.
37.
38. SimpleDateFormat timeStampFormat = new SimpleDateFormat("dd-MM-yyyy_HH:mm");
39. Date myDate = new Date();
40. String nomeArquivo = "f"+ timeStampFormat.format(myDate) + ".jpg";
41.
42. this.imageFile = new File(fotoDir, nomeArquivo);
43. }
44.
45. public void tirarFotoOnClick(View view){
46. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
47. intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
48. startActivityForResult(intent, REQUEST_PICTURE);
49. }
50.
51. @Override
52. protected void onActivityResult(int requestCode, int resultCode, Intent data){
53. if (requestCode == REQUEST_PICTURE && resultCode == RESULT_OK) {
54. FileInputStream fis = null;
55.
56. try {
57. try{
58. fis = new FileInputStream(imageFile);
59. Bitmap picture = BitmapFactory.decodeStream(fis);
60. imageView.setImageBitmap(picture);
61.
62. } finally {
63. if(fis != null){
64. try {
65. fis.close();
66. } catch (IOException e) {e.printStackTrace();}
67. }
68. }
69. }
70. catch (FileNotFoundException e) {e.printStackTrace();}
71.
72. }
73. }
74. }
Visualizamos das linhas 03 a 20 as classes necessárias para seu funcionamento dentro da estrutura do aplicativo. Em seguida, a classe principal é declarada (linha 22), assim como os componentes da tela (linhas 24 e 25) e a constante RESQUEST_PICTURE (linha 26) utilizada para identificação da chamada de uma nova Activity pelo método onActivityResult().
O método onCreate() – linha 29 – recupera o componente responsável pela imagem (linha 33), assim como o caminho onde são armazenadas por padrão as imagens no device (linha 35). A imagem terá o nome gerado a partir da data e hora do sistema e será armazenada com a extensão .jpg. (linhas 38 a 42).
Ao clicar no botão “Tirar foto”, o método tirarFotoOnClick (linha 45) é executado, o qual inicia o aplicativo de câmera do celular.
Ao retornar da tela da câmera, se a captura foi confirmada (linha 53), a imagem é recuperada e apresentada na tela (linhas 58 a 60).
Imagens, assim como sons, são tratadas do ponto de vista computacional como um array de bytes. Para simplificar seu uso, em especial em linguagens orientadas a objetos, elas são tratadas como objetos do tipo InputStream (fluxo de byte). Essa classe possui subclasses com funções específicas. Uma delas é a FileInputStream (linha 58), a qual permite criar um fluxo de Stream a partir do conteúdo de um arquivo.
A partir do InputStream com o conteúdo da imagem existente no arquivo, pode-se instanciar um objeto BitMap (linha 59), sendo esse um dos formatos compatíveis para o componente visual ImageView apresentar uma imagem definida a partir do método setImageBitmap (linha 60).
A Figura 6 apresenta o aplicativo em execução em um device real.
Desenvolvendo um aplicativo para captura de vídeo
O aplicativo de captura de vídeo é muito semelhante ao aplicativo de captura de foto. Contudo, no aplicativo de vídeo será apresentada para o usuário uma interface contendo dois ImageViews no centro da tela, um sobreposto ao outro. O maior ImageView terá um preview do vídeo e o menor terá uma figura de play para informar que se trata de um vídeo. Teremos também um botão de “Gravar Vídeo” na parte inferior da tela.
Ao clicar no botão, será apresentado o aplicativo da câmera do device, o qual permitirá a gravação e algumas configurações, como a qualidade do vídeo. Após a gravação, uma tela de confirmação será apresentada, e por fim, será apresentada a tela inicial com o preview do vídeo. O código da interface gráfica do aplicativo é apresentado na Listagem 9.
01. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
02. xmlns:tools="http://schemas.android.com/tools"
03. android:layout_width="match_parent"
04. android:layout_height="match_parent"
05. tools:context=".PrincipalActivity"
06. android:orientation="vertical" >
07.
08. <View
09. android:layout_width="match_parent"
10. android:layout_height="1dp"
11. android:layout_marginBottom="5dp"
12. android:layout_marginTop="5dp"
13. android:background="#ddd" />
14.
15. <TextView
16. android:layout_width="fill_parent"
17. android:layout_height="wrap_content"
18. android:gravity="center"
19. android:text="Video" />
20.
21. <View
22. android:layout_width="match_parent"
23. android:layout_height="1dp"
24. android:layout_marginBottom="5dp"
25. android:layout_marginTop="5dp"
26. android:background="#ddd" />
27.
28. <FrameLayout
29. android:layout_width="match_parent"
30. android:layout_height="355dp">
31.
32. <ImageView
33. android:id="@+id/preview"
34. android:layout_width="match_parent"
35. android:layout_height="355dp" />
36.
37. <ImageView
38. android:id="@+id/icon"
39. android:layout_width="50dp"
40. android:layout_height="50dp"
41. android:layout_gravity="center" />
42.
43. </FrameLayout>
44.
45. <LinearLayout
46. android:layout_width="match_parent"
47. android:layout_height="wrap_content"
48. android:orientation="horizontal" >
49.
50. <Button
51. android:layout_width="match_parent"
52. android:layout_height="wrap_content"
53. android:onClick="gravarVideoOnClick"
54. android:text="Gravar Video" />
55. </LinearLayout>
56.
57. </LinearLayout>
Definimos também um componente FrameLayout (linha 28) para comportar dois componentes ImageView (linhas 32 e 37). O primeiro, cujo id é preview, tem a funcionalidade de exibir uma imagem de preview do vídeo. Já o segundo, identificado como icon, tem a função de adicionar um ícone sobre a imagem para simbolizar que estamos visualizando um vídeo. Foi adicionado um layout LinearLayout (linha 45) definido no formato horizontal, a fim de comportar um componente Button (linha 50) responsável por iniciar a captura de vídeo.
O código Java do aplicativo é apresentado na Listagem 10.
01. package br.edu.utfpr.usandocameravideo;
02.
03. import java.io.File;
04. import java.text.SimpleDateFormat;
05. import java.util.Date;
06.
07. import android.app.Activity;
08. import android.content.Intent;
09. import android.graphics.Bitmap;
10. import android.media.ThumbnailUtils;
11. import android.net.Uri;
12. import android.os.Bundle;
13. import android.os.Environment;
14. import android.provider.MediaStore;
15. import android.view.View;
16. import android.widget.ImageView;
17.
18. public class PrincipalActivity extends Activity {
19.
20. private File filmFile;
21.
22. private ImageView preview;
23. private ImageView icon;
24.
25. private static final int REQUEST_FILM = 1000;
26.
27. @Override
28. protected void onCreate(Bundle savedInstanceState) {
29. super.onCreate(savedInstanceState);
30. setContentView(R.layout.activity_principal);
31.
32. preview = (ImageView) findViewById(R.id.preview);
33. icon = (ImageView) findViewById(R.id.icon);
34.
35. File filmDir = Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_MOVIES);
36.
37.
38. SimpleDateFormat timeStampFormat = new SimpleDateFormat("dd-MM- yyyy_HH:mm");
39.
40. Date myDate = new Date();
41. String nomeArquivo = "v" + timeStampFormat.format(myDate) + ".mp4";
42.
43. this.filmFile = new File(filmDir, nomeArquivo);
44. }
45.
46. public void gravarVideoOnClick(View view){
47. Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
48. intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(filmFile));
49. startActivityForResult(intent, REQUEST_FILM);
50. }
51.
52. @Override
53. protected void onActivityResult(int requestCode, int resultCode, Intent data){
54. if (requestCode == REQUEST_FILM && resultCode == RESULT_OK) {
55. Bitmap thumb = ThumbnailUtils.createVideoThumbnail(filmFile.
getAbsolutePath(), MediaStore.Images.Thumbnails.MINI_KIND);
56.
57.
58. preview.setImageBitmap(thumb);
59. icon.setImageResource(R.drawable.play);
60.
61. }
62. }
63. }
O código deste aplicativo é muito semelhante ao que acabamos de desenvolver, diferenciando na utilização de duas imagens na tela. Essas são declaradas nas linhas 22 e 23 e recuperadas nas linhas 32 e 33.
Após isso, recuperamos o caminho do armazenamento dos vídeos no device (linha 35) e formatamos o nome do vídeo que será armazenado (linha 38 a 43).
Para gravar o vídeo, o aplicativo da câmera do celular é chamado (linhas 47 a 49), e no seu retorno (método onActivityResult() – linha 53) criamos uma prévia do vídeo capturado (linha 55), assim como adicionamos na tela essa prévia (linha 58) e o símbolo de play (linha 59).
Para o símbolo, foi adicionado na pasta drawble do projeto uma imagem play.png (Figura 7). Esta figura tem como característica ter um fundo transparente para não cobrir parte do preview.
Após de realizada a execução em um dispositivo real, o resultado é visto na Figura 8.
Este artigo teve como objetivo conceituar o uso de recursos multimídias em aplicações Android.
Esses aplicativos podem ser incrementados com novas funcionalidades, como uma barra de reprodução para o aplicativo de reprodução de áudio e vídeo, um player de arquivos de áudio e vídeo online (disponível na nuvem, como pastas do Google Drive ou Dropbox), ou até mesmo um diário multimídia, no qual a pessoa pode registrar várias vezes ao dia recursos de áudio e vídeo a partir do seu celular, armazenar esses dados em servidores e reproduzi-los sempre que necessário.