Android 画中画与音频录制播放开发指南
1. Android 画中画功能实现
1.1 进入画中画模式
要在 Android 应用中实现画中画(PiP)模式,首先需要在 MainActivity.java 类文件中添加 enterPipMode 的点击回调方法。以下是具体步骤:
1. 定位 MainActivity.java 文件,并在代码编辑器中打开。
2. 添加以下代码:
import android.app.PictureInPictureParams;
import android.util.Rational;
public void enterPipMode(View view) {
Rational rational = new Rational(binding.videoView1.getWidth(),
binding.videoView1.getHeight());
PictureInPictureParams params =
new PictureInPictureParams.Builder()
.setAspectRatio(rational)
.build();
binding.pipButton.setVisibility(View.INVISIBLE);
binding.videoView1.setMediaController(null);
enterPictureInPictureMode(params);
}
此方法首先获取 Button 视图的引用,然后创建一个包含 VideoView 宽度和高度的 Rational 对象。接着使用 PictureInPictureParams.Builder 创建一组画中画参数,并将 Rational 对象作为视频播放的宽高比传入。由于在画中画模式下按钮不需要可见,所以将其设置为不可见,同时隐藏视频播放控件,确保在画中画模式下视频视图不受阻碍。
编译并在运行 Android 8 或更高版本的设备或模拟器上运行应用,等待视频播放开始后,点击画中画模式按钮,视频播放将缩小并显示在画中画窗口中。
1.2 检测画中画模式变化
为了检测画中画模式的变化,需要重写 onPictureInPictureModeChanged() 方法。在 MainActivity.java 文件中添加以下代码:
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
if (isInPictureInPictureMode) {
} else {
binding.pipButton.setVisibility(View.VISIBLE);
binding.videoView1.setMediaController(mediaController);
}
}
当该方法被调用时,会传入一个布尔值,指示活动是否处于画中画模式。代码通过检查此值来决定是否显示画中画按钮并重新激活播放控件。
1.3 添加广播接收器
为了在画中画窗口中添加一个操作,以显示包含当前播放视频名称的 Toast 消息,需要实现一个广播接收器,并使用待处理意图从画中画窗口向活动广播消息。在 onPictureInPictureModeChanged() 方法中添加以下代码:
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver receiver;
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
if (isInPictureInPictureMode) {
IntentFilter filter = new IntentFilter();
filter.addAction(
"com.ebookfrenzy.videoplayer.VIDEO_INFO");
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context,
Intent intent) {
Toast.makeText(context,
"Favorite Home Movie Clips",
Toast.LENGTH_LONG).show();
}
};
registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
} else {
binding.pipButton.setVisibility(View.VISIBLE);
binding.videoView1.setMediaController(mediaController);
if (receiver != null) {
unregisterReceiver(receiver);
}
}
}
}
1.4 添加画中画操作
1.4.1 准备图标文件
使用名为 ic_info_24dp.xml 的图像图标文件来表示画中画窗口中的操作。可以从以下 URL 下载源代码存档中的 project_icons 文件夹中找到该文件:
https://www.ebookfrenzy.com/retail/giraffejava/index.php
将该图标文件复制并粘贴到项目工具窗口中的 app -> res -> drawables 文件夹中。
1.4.2 创建意图和待处理意图
在 MainActivity.java 文件中添加以下方法来创建 Intent 和 PendingIntent 对象:
import android.app.PendingIntent;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE = 101;
private void createPipAction() {
Intent actionIntent =
new Intent("com.ebookfrenzy.videoplayer.VIDEO_INFO");
final PendingIntent pendingIntent =
PendingIntent.getBroadcast(MainActivity.this,
REQUEST_CODE, actionIntent, FLAG_IMMUTABLE);
}
}
1.4.3 创建远程操作对象
在 createPipAction() 方法中添加以下代码来创建 RemoteAction 对象:
import android.app.RemoteAction;
import android.graphics.drawable.Icon;
import java.util.ArrayList;
private void createPipAction() {
final ArrayList<RemoteAction> actions = new ArrayList<>();
Intent actionIntent =
new Intent("com.ebookfrenzy.videoplayer.VIDEO_INFO");
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
MainActivity.this,
REQUEST_CODE, actionIntent, FLAG_IMMUTABLE);
final Icon icon =
Icon.createWithResource(MainActivity.this,
R.drawable.ic_info_24dp);
RemoteAction remoteAction = new RemoteAction(icon, "Info",
"Video Info", pendingIntent);
actions.add(remoteAction);
PictureInPictureParams params =
new PictureInPictureParams.Builder()
.setActions(actions)
.build();
setPictureInPictureParams(params);
}
最后,在 onPictureInPictureModeChanged() 方法中调用 createPipAction() 方法,以便在活动进入画中画模式时执行该操作。
1.5 测试画中画操作
重新运行应用并将活动置于画中画模式,点击画中画窗口,新的操作按钮将出现。点击操作按钮,等待 Toast 消息显示视频名称。
2. Android 音频录制与播放
2.1 音频播放
大多数 Android 实现支持多种音频格式,如 AAC LC/LTP、HE - AACv1(AAC +)、HE - AACv2(增强 AAC +)、AMR - NB、AMR - WB、MP3、MIDI、Ogg Vorbis 和 PCM/WAVE 等。音频播放可以使用 MediaPlayer 或 AudioTrack 类。 MediaPlayer 类提供了更简单的编程接口,能满足大多数音频播放需求。
MediaPlayer 类的一些关键方法如下表所示:
| 方法 | 描述 |
| ---- | ---- |
| create() | 创建类的新实例,传入要播放音频的 Uri |
| setDataSource() | 设置音频播放的源 |
| prepare() | 指示播放器准备开始播放 |
| start() | 开始播放 |
| pause() | 暂停播放,可通过调用 resume() 方法恢复 |
| stop() | 停止播放 |
| setVolume() | 设置左右声道的播放音量 |
| resume() | 恢复之前暂停的播放会话 |
| reset() | 重置媒体播放器实例的状态 |
| release() | 释放播放器占用的资源 |
以下是一个典型的音频播放示例:
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource("https://www.yourcompany.com/myaudio.mp3");
mediaPlayer.prepare();
mediaPlayer.start();
2.2 音频录制
使用 MediaRecorder 类可以进行音频录制,该类的一些关键方法如下:
| 方法 | 描述 |
| ---- | ---- |
| setAudioSource() | 指定要录制的音频源,通常为设备麦克风 |
| setVideoSource() | 指定要录制的视频源,如摄像头 |
| setOutputFormat() | 指定录制的音频或视频的存储格式 |
| setAudioEncoder() | 指定录制音频的编码器 |
| setOutputFile() | 配置录制的音频或视频文件的存储路径 |
| prepare() | 准备 MediaRecorder 实例开始录制 |
| start() | 开始录制过程 |
| stop() | 停止录制过程,停止后需要重新配置和准备才能再次启动 |
| reset() | 重置录制器,需要重新配置和准备才能再次启动 |
| release() | 释放录制器占用的资源 |
以下是一个典型的音频录制示例:
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setOutputFile(audioFilePath);
mediaRecorder.prepare();
mediaRecorder.start();
// 录制结束
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();
要进行音频录制,应用的清单文件必须包含 android.permission.RECORD_AUDIO 权限:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2.3 示例项目创建
2.3.1 创建项目
从欢迎屏幕中选择“新建项目”选项,在新建项目对话框中选择“Empty Views Activity”模板,然后点击“下一步”。在“名称”字段中输入 AudioApp ,并指定 com.ebookfrenzy.audioapp 作为包名。在点击“完成”按钮之前,将“最低 API 级别”设置为 API 31:Android 12.0,将“语言”菜单设置为 Java,并按照第 11.8 节“迁移项目到视图绑定”中的步骤为项目添加视图绑定支持。
2.3.2 设计用户界面
- 项目创建完成后,从项目工具窗口中选择
activity_main.xml文件,在布局编辑器的设计模式下,选择“Hello World!”TextView并从布局中删除。 - 拖放三个
Button视图到布局中,将按钮配置为显示“Play”、“Record”和“Stop”字符串资源,并分别为它们设置视图 ID 为playButton、recordButton和stopButton。 - 选择“Play”按钮,在“属性”面板中,将
onClick属性配置为在用户选择时调用名为playAudio的方法。对其余按钮重复此步骤,分别配置为调用recordAudio和stopAudio方法。
2.4 检查麦克风可用性
为了避免在没有麦克风的设备上录制音频时抛出异常,需要检查设备上是否存在麦克风。在 MainActivity.java 文件中添加以下方法:
package com.ebookfrenzy.audioapp;
import android.content.pm.PackageManager;
public class MainActivity extends AppCompatActivity {
protected boolean hasMicrophone() {
PackageManager pmanager = this.getPackageManager();
return pmanager.hasSystemFeature(
PackageManager.FEATURE_MICROPHONE);
}
}
2.5 初始化活动
在 MainActivity.java 文件中修改 onCreate() 方法和添加 audioSetup() 方法:
package com.ebookfrenzy.audioapp;
import java.io.File;
import java.io.IOException;
import androidx.annotation.NonNull;
import android.media.MediaRecorder;
import android.os.Environment;
import android.media.MediaPlayer;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private static MediaRecorder mediaRecorder;
private static MediaPlayer mediaPlayer;
private static String audioFilePath;
private boolean isRecording = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
audioSetup();
}
private void audioSetup() {
if (!hasMicrophone()) {
binding.stopButton.setEnabled(false);
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
} else {
binding.playButton.setEnabled(false);
binding.stopButton.setEnabled(false);
}
File audioFile = new File(this.getFilesDir(), "myaudio.3gp");
audioFilePath = audioFile.getAbsolutePath();
}
}
此代码调用 hasMicrophone() 方法来确定设备是否包含麦克风。如果没有,则禁用所有按钮;否则,仅禁用“Stop”和“Play”按钮。同时,在应用的内部存储中创建一个名为 myaudio.3gp 的新文件来存储音频录制。
2.6 实现录制、停止和播放方法
2.6.1 录制方法
public void recordAudio (View view) {
isRecording = true;
binding.stopButton.setEnabled(true);
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
try {
mediaRecorder = new MediaRecorder(this);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(
MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(audioFilePath);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.prepare();
} catch (Exception e) {
e.printStackTrace();
}
mediaRecorder.start();
}
2.6.2 停止方法
public void stopAudio (View view) {
binding.stopButton.setEnabled(false);
binding.playButton.setEnabled(true);
if (isRecording) {
binding.recordButton.setEnabled(false);
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
isRecording = false;
} else {
mediaPlayer.release();
mediaPlayer = null;
binding.recordButton.setEnabled(true);
}
}
2.6.3 播放方法
public void playAudio (View view) throws IOException {
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
binding.stopButton.setEnabled(true);
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(audioFilePath);
mediaPlayer.prepare();
mediaPlayer.start();
}
2.7 配置和请求权限
2.7.1 在清单文件中添加权限
在 AndroidManifest.xml 文件中添加以下权限标签:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
2.7.2 运行时请求权限
在 MainActivity.java 文件中添加以下代码:
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.widget.Toast;
import android.Manifest;
public class MainActivity extends AppCompatActivity {
private static final int RECORD_REQUEST_CODE = 101;
protected void requestPermission(String permissionType, int requestCode) {
int permission = ContextCompat.checkSelfPermission(this,
permissionType);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{permissionType}, requestCode
);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
此代码用于验证指定的权限是否已经被授予,如果未授予,则请求权限。当权限请求处理完成后, onRequestPermissionsResult() 方法将被调用。
2.8 运行时权限请求流程图
下面是运行时权限请求的 mermaid 流程图:
graph TD;
A[开始] --> B[检查权限是否已授予];
B -- 已授予 --> C[不请求权限];
B -- 未授予 --> D[请求权限];
D --> E[等待权限请求结果];
E --> F[处理权限请求结果];
F --> G[结束];
2.9 完整代码示例及说明
以下是 MainActivity.java 的完整代码示例,整合了前面提到的所有功能:
package com.ebookfrenzy.audioapp;
import java.io.File;
import java.io.IOException;
import androidx.annotation.NonNull;
import android.content.pm.PackageManager;
import android.media.MediaRecorder;
import android.os.Environment;
import android.media.MediaPlayer;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.widget.Toast;
import android.Manifest;
import android.view.View;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import com.ebookfrenzy.audioapp.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private static MediaRecorder mediaRecorder;
private static MediaPlayer mediaPlayer;
private static String audioFilePath;
private boolean isRecording = false;
private static final int RECORD_REQUEST_CODE = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
audioSetup();
requestPermission(Manifest.permission.RECORD_AUDIO, RECORD_REQUEST_CODE);
}
private void audioSetup() {
if (!hasMicrophone()) {
binding.stopButton.setEnabled(false);
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
} else {
binding.playButton.setEnabled(false);
binding.stopButton.setEnabled(false);
}
File audioFile = new File(this.getFilesDir(), "myaudio.3gp");
audioFilePath = audioFile.getAbsolutePath();
}
protected boolean hasMicrophone() {
PackageManager pmanager = this.getPackageManager();
return pmanager.hasSystemFeature(
PackageManager.FEATURE_MICROPHONE);
}
public void recordAudio(View view) {
isRecording = true;
binding.stopButton.setEnabled(true);
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
try {
mediaRecorder = new MediaRecorder(this);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(
MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setOutputFile(audioFilePath);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.prepare();
} catch (Exception e) {
e.printStackTrace();
}
mediaRecorder.start();
}
public void stopAudio(View view) {
binding.stopButton.setEnabled(false);
binding.playButton.setEnabled(true);
if (isRecording) {
binding.recordButton.setEnabled(false);
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
isRecording = false;
} else {
mediaPlayer.release();
mediaPlayer = null;
binding.recordButton.setEnabled(true);
}
}
public void playAudio(View view) throws IOException {
binding.playButton.setEnabled(false);
binding.recordButton.setEnabled(false);
binding.stopButton.setEnabled(true);
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(audioFilePath);
mediaPlayer.prepare();
mediaPlayer.start();
}
protected void requestPermission(String permissionType, int requestCode) {
int permission = ContextCompat.checkSelfPermission(this,
permissionType);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{permissionType}, requestCode
);
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
代码说明:
- onCreate() 方法:在活动创建时进行初始化操作,包括视图绑定、音频设置和权限请求。
- audioSetup() 方法:检查设备是否有麦克风,并根据情况启用或禁用按钮,同时设置音频文件的存储路径。
- hasMicrophone() 方法:检查设备是否有麦克风。
- recordAudio() 方法:处理录制音频的逻辑,包括配置 MediaRecorder 并开始录制。
- stopAudio() 方法:处理停止录制或播放的逻辑,根据当前状态释放相应的资源。
- playAudio() 方法:处理播放音频的逻辑,包括配置 MediaPlayer 并开始播放。
- requestPermission() 方法:检查并请求所需的权限。
- onRequestPermissionsResult() 方法:处理权限请求的结果。
3. 总结
3.1 画中画功能总结
通过以上步骤,我们成功实现了 Android 应用中的画中画功能。从进入画中画模式、检测模式变化,到添加广播接收器和画中画操作,每一步都有明确的代码实现和操作步骤。画中画功能为用户提供了更加灵活的视频观看体验,在视频播放类应用中具有很大的实用价值。
3.2 音频录制与播放总结
在音频录制与播放方面,我们使用 MediaPlayer 和 MediaRecorder 类实现了基本的音频录制和播放功能。同时,我们还考虑了设备麦克风的可用性检查、权限请求以及文件存储等问题。整个过程涵盖了从项目创建、界面设计到代码实现的各个方面,为开发者提供了一个完整的示例。
3.3 注意事项
- 在使用画中画功能时,要确保设备运行的是 Android 8 或更高版本。
- 进行音频录制时,一定要请求
android.permission.RECORD_AUDIO权限,特别是在 Android 6 及更高版本的设备上,需要在运行时请求权限。 - 在操作
MediaPlayer和MediaRecorder时,要注意资源的释放,避免出现内存泄漏等问题。
通过本文的介绍,开发者可以根据自己的需求对这些功能进行进一步的扩展和优化,开发出更加丰富和实用的 Android 应用。
超级会员免费看
31

被折叠的 条评论
为什么被折叠?



