`

【翻译】(83)媒体回放

 
阅读更多

【翻译】(83)媒体回放

 

see

http://developer.android.com/guide/topics/media/mediaplayer.html

 

原文见

http://developer.android.com/guide/topics/media/mediaplayer.html

 

-------------------------------

 

Media Playback

 

媒体回放

 

-------------------------------

 

In this document

 

本文目录

 

* The Basics 基础

* Manifest Declarations 清单声明

* Using MediaPlayer 使用MediaPlayer

* Asynchronous Preparation 异步的准备

* Managing State 管理状态

* Releasing the MediaPlayer 释放MediaPlayer

* Using a Service with MediaPlayer 使用带MediaPlayer的服务

* Running asynchronously 异步地运行

* Handling asynchronous errors 处理异步错误

* Using wake locks 使用醒锁

* Running as a foreground service 运行作为一个前台服务

* Handling audio focus 处理音频焦点

* Performing cleanup 执行清除

* Handling the AUDIO_BECOMING_NOISY Intent 处理AUDIO_BECOMING_NOISY意图

* Retrieving Media from a Content Resolver 从一个内容解析器中取出媒体

 

Key classes

 

关键类

 

MediaPlayer

AudioManager

SoundPool

 

See also

 

另见

 

JetPlayer JetPlayer

Audio Capture 音频捕捉

Android Supported Media Formats Android支持的媒体格式

Data Storage 数据存储

 

-------------------------------

 

The Android multimedia framework includes support for playing variety of common media types, so that you can easily integrate audio, video and images into your applications. You can play audio or video from media files stored in your application's resources (raw resources), from standalone files in the filesystem, or from a data stream arriving over a network connection, all using MediaPlayer APIs.

 

Android多媒体框架包含对播放各种通用媒体类型的支持,使你可以轻易继承音频,视频和图片进你的应用程序。你可以播放音频或视频,从存储在你的应用程序资源中的媒体文件,从文件系统中的独立文件,或从通过网络连接到达的数据流,所有都使用MediaPlayer的API。

 

This document shows you how to write a media-playing application that interacts with the user and the system in order to obtain good performance and a pleasant user experience.

 

本文向你展示如何编写一个媒体播放应用程序,它与用户和系统交互以便取得好的性能和令人愉悦的用户体验。

 

-------------------------------

 

Note: You can play back the audio data only to the standard output device. Currently, that is the mobile device speaker or a Bluetooth headset. You cannot play sound files in the conversation audio during a call.

 

注意:你可以只回放音频数据到标准输出设备。当前,那是移动设备扬声器或一个蓝牙耳机。你不能在一次通话中以会话音频的形式播放声音文件(注:待考)。

 

-------------------------------

 

The Basics

 

基础

 

The following classes are used to play sound and video in the Android framework:

 

以下类被用于在Android框架内播放声音和视频:

 

MediaPlayer

 

This class is the primary API for playing sound and video.

 

这个类是用于播放音频和视频的主要API。

 

AudioManager

 

This class manages audio sources and audio output on a device.

 

这个类管理一台设备上的音频源和音频输出。

 

-------------------------------

 

Manifest Declarations

 

清单声明

 

Before starting development on your application using MediaPlayer, make sure your manifest has the appropriate declarations to allow use of related features.

 

从使用MediaPlayer在你的应用程序上开始开发之前,请确保你的清单拥有合适的声明以允许相关特性的使用。

 

* Internet Permission - If you are using MediaPlayer to stream network-based content, your application must request network access.

 

* 互联网权限——如果你正在使用MediaPlayer来流读取基于网络的内容,你的应用程序必须请求网络访问权。

 

<uses-permission android:name="android.permission.INTERNET" />

 

* Wake Lock Permission - If your player application needs to keep the screen from dimming or the processor from sleeping, or uses the MediaPlayer.setScreenOnWhilePlaying() or MediaPlayer.setWakeMode() methods, you must request this permission.

 

* 醒锁权限(注:醒锁,也被称为wakelock,是Android系统特有的,是Linux系统允许调用方阻止系统进入低电源状态的一组补丁)——如果你的播放器应用程序需要保持屏幕不变暗或处理器不休眠,或者使用MediaPlayer.setScreenOnWhilePlaying()或MediaPlayer.setWakeMode()方法,你必须请求这个权限。

 

<uses-permission android:name="android.permission.WAKE_LOCK" />

 

-------------------------------

 

Using MediaPlayer

 

使用MediaPlayer

 

One of the most important components of the media framework is the MediaPlayer class. An object of this class can fetch, decode, and play both audio and video with minimal setup. It supports several different media sources such as:

 

媒体框架中一个最重要的组件是MediaPlayer类。这个类的一个对象可以用最小的配置取出、解码和播放音频和视频两者。它支持几种不同的媒体源诸如:

 

* Local resources

 

* 本地资源

 

* Internal URIs, such as one you might obtain from a Content Resolver

 

* 内部URI,诸如你可能从一个内容解析器中取得的URI

 

* External URLs (streaming)

 

* 外部URL(流)

 

For a list of media formats that Android supports, see the Android Supported Media Formats document.

 

想获得Android支持的媒体格式的列表,请参见Android支持的媒体格式文档。

 

Here is an example of how to play audio that's available as a local raw resource (saved in your application's res/raw/ directory):

 

这里有一个示例,关于如何播放作为一个本地原始资源而可用的音频(被保存在你的应用程序的res/raw目录中):

 

-------------------------------

 

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);

mediaPlayer.start(); // no need to call prepare(); create() does that for you 不需要调用prepare();create()为你做那件事

 

-------------------------------

 

In this case, a "raw" resource is a file that the system does not try to parse in any particular way. However, the content of this resource should not be raw audio. It should be a properly encoded and formatted media file in one of the supported formats.

 

在这种情况下,一个“原始”资源是一个系统不尝试以任意特殊方式解析的文件。然而,这个资源的内容应该不是原始音频。它应该是一个以其中一种支持格式正确地被解码和被格式化的媒体文件。

 

And here is how you might play from a URI available locally in the system (that you obtained through a Content Resolver, for instance):

 

而且这里是你可能如何从系统中本地可用的一个URI中播放(例如,你通过一个内容解析器获取它):

 

-------------------------------

 

Uri myUri = ....; // initialize Uri here 在这里初始化Uri

MediaPlayer mediaPlayer = new MediaPlayer();

mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

mediaPlayer.setDataSource(getApplicationContext(), myUri);

mediaPlayer.prepare();

mediaPlayer.start();

 

-------------------------------

 

Playing from a remote URL via HTTP streaming looks like this:

 

从一个远程URL中通过HTTP流播放,看起来像这样:

 

-------------------------------

 

String url = "http://........"; // your URL here 这里是你的URL

MediaPlayer mediaPlayer = new MediaPlayer();

mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

mediaPlayer.setDataSource(url);

mediaPlayer.prepare(); // might take long! (for buffering, etc) 可能花很长时间!(为了缓冲,等)

mediaPlayer.start();

 

-------------------------------

 

-------------------------------

 

Note: If you're passing a URL to stream an online media file, the file must be capable of progressive download.

 

注意:如果你正在传递一个URL以流处理(注:流式传输)一个在线音频文件,那么该文件必须能够渐进式下载(注:渐进式下载和流式下载的区别是:对于渐进式下载,曾经播放过的数据被保留下来以便再次播放;而流式下载则不保留曾经播放过的数据)。

 

-------------------------------

 

-------------------------------

 

Caution: You must either catch or pass IllegalArgumentException and IOException when using setDataSource(), because the file you are referencing might not exist.

 

警告:当使用setDataSource()时你必须捕捉或传递IllegalArgumentException和IOException,因为你正在引用的文件可能不存在。

 

-------------------------------

 

Asynchronous Preparation

 

异步的准备

 

Using MediaPlayer can be straightforward in principle. However, it's important to keep in mind that a few more things are necessary to integrate it correctly with a typical Android application. For example, the call to prepare() can take a long time to execute, because it might involve fetching and decoding media data. So, as is the case with any method that may take long to execute, you should never call it from your application's UI thread. Doing that will cause the UI to hang until the method returns, which is a very bad user experience and can cause an ANR (Application Not Responding) error. Even if you expect your resource to load quickly, remember that anything that takes more than a tenth of a second to respond in the UI will cause a noticeable pause and will give the user the impression that your application is slow.

 

使用MediaPlayer原则上可以是直截了当的。然而,重要的是谨记正确地集成它和一个典型Android应用程序需要更多一些东西。例如,对于prepare()的调用可以花长时间来运行,因为它可能涉及取出和解码媒体数据。所以,正如任意可能花长时间运行的方法的情况那样,你应该从不从你的应用程序的用户界面线程中调用它。那样做将导致用户界面挂起直至方法返回,这是非常不好的用户体验并且可能导致一个ANR(应用程序无响应)错误。即便你期待你的资源快速地加载,请记住在用户界面中花费多于十分之一秒来响应的任意事情将导致一个明显的停顿(注:暂停)并将给予用户你的应用程序慢的印象。

 

To avoid hanging your UI thread, spawn another thread to prepare the MediaPlayer and notify the main thread when done. However, while you could write the threading logic yourself, this pattern is so common when using MediaPlayer that the framework supplies a convenient way to accomplish this task by using the prepareAsync() method. This method starts preparing the media in the background and returns immediately. When the media is done preparing, the onPrepared() method of the MediaPlayer.OnPreparedListener, configured through setOnPreparedListener() is called.

 

为了避免挂起你的用户界面线程,请生成另一个线程来准备MediaPlayer并且在完成时通知主线程。然而,当你可以自己编写线程逻辑时,这种模式在使用MediaPlayer时是非常普遍的,以致于框架提供一个便利方式来通过prepareAsync()方法完成这个任务。这个方法开始在后台准备媒体并且立刻返回。当方法完成准备时,MediaPlayer.OnPreparedListener的通过setOnPreparedListener()被配置的onPrepared()方法被调用。

 

Managing State

 

管理状态

 

Another aspect of a MediaPlayer that you should keep in mind is that it's state-based. That is, the MediaPlayer has an internal state that you must always be aware of when writing your code, because certain operations are only valid when then player is in specific states. If you perform an operation while in the wrong state, the system may throw an exception or cause other undesireable behaviors.

 

你应该谨记的MediaPlayer的另一个切面是它是基于状态的。就是说,MediaPlayer拥有一个内部状态,当你编写你的代码时你必须总是意识到它,因为某个操作只在播放器处于特定状态时才可用。如果你执行一个操作在错误的状态中,系统可能抛出一个异常或导致其它不期望的行为。

 

The documentation in the MediaPlayer class shows a complete state diagram, that clarifies which methods move the MediaPlayer from one state to another. For example, when you create a new MediaPlayer, it is in the Idle state. At that point, you should initialize it by calling setDataSource(), bringing it to the Initialized state. After that, you have to prepare it using either the prepare() or prepareAsync() method. When the MediaPlayer is done preparing, it will then enter the Prepared state, which means you can call start() to make it play the media. At that point, as the diagram illustrates, you can move between the Started, Paused and PlaybackCompleted states by calling such methods as start(), pause(), and seekTo(), amongst others. When you call stop(), however, notice that you cannot call start() again until you prepare the MediaPlayer again.

 

MediaPlayer类中的文档展示一个完整的状态图(注:状态图是有限状态自动机的图形表示),它阐明哪些方法移动MediaPlayer从一个状态到另一个状态。例如,当你创建一个新MediaPlayer时,它正处于空闲状态。在那时,你不得不使用prepare()或prepareAsync()方法来准备它。当MediaPlayer完成准备时,之后它将进入已准备状态,这意味着你可以调用start()来让它播放媒体。在那时,正如图中所示,你可以通过调用诸如start()、pause()和seekTo()的方法在启动、暂停、和完成回放的状态之间相互地(注:待考)移动。然而,当你调用stop()时,请注意你不能再次调用start(),直至你再次准备该MediaPlayer。

 

Always keep the state diagram in mind when writing code that interacts with a MediaPlayer object, because calling its methods from the wrong state is a common cause of bugs.

 

当编写与一个MediaPlayer对象交互的代码时请总是记住状态图,因为从错误的状态中调用它的方法是缺陷的一个通常起因。

 

Releasing the MediaPlayer

 

释放

 

A MediaPlayer can consume valuable system resources. Therefore, you should always take extra precautions to make sure you are not hanging on to a MediaPlayer instance longer than necessary. When you are done with it, you should always call release() to make sure any system resources allocated to it are properly released. For example, if you are using a MediaPlayer and your activity receives a call to onStop(), you must release the MediaPlayer, because it makes little sense to hold on to it while your activity is not interacting with the user (unless you are playing media in the background, which is discussed in the next section). When your activity is resumed or restarted, of course, you need to create a new MediaPlayer and prepare it again before resuming playback.

 

一个MediaPlayer可以消耗昂贵的系统资源。因此,你应该总是采取额外的预防措施来确保你不是比必需的更长地一直持有一个MediaPlayer实例。当你用完它时,你应该总是调用release()来确保分配给它的任意系统资源被正确地释放。例如,如果你正在使用一个MediaPlayer而你的活动接收一个对onStop()的调用,你必须释放MediaPlayer,因为在你的活动不与用户交互时一直持有它,是没有多大意义的(除非你正在在后台中播放媒体,这在下一个章节中讨论)。当你的活动被恢复或被重启,当然,你需要创建一个新的MediaPlayer和在恢复回放之前再次准备它。

 

Here's how you should release and then nullify your MediaPlayer:

 

这里是你应该如何释放然后对你的MediaPlayer赋空值:

 

-------------------------------

 

mediaPlayer.release();

mediaPlayer = null;

 

-------------------------------

 

As an example, consider the problems that could happen if you forgot to release the MediaPlayer when your activity is stopped, but create a new one when the activity starts again. As you may know, when the user changes the screen orientation (or changes the device configuration in another way), the system handles that by restarting the activity (by default), so you might quickly consume all of the system resources as the user rotates the device back and forth between portrait and landscape, because at each orientation change, you create a new MediaPlayer that you never release. (For more information about runtime restarts, see Handling Runtime Changes.)

 

作为一个示例,请考虑如果你在你的活动被停止时忘记释放MediaPlayer,但在活动再次启动时创建一个新实例,可能发生的问题、正如你可能知道的,当用户改变屏幕方向(或以另一种方式改变设备配置),系统通过重启活动来处理它们(默认下),那样你可能在用户在竖屏和宽屏之间旋转来回设备的时候快速地消耗所有系统资源,因为在每次方向改变上,你创建一个新的MediaPlayer,你从不释放它。(关于运行时重启的更多信息,参见处理运行时改变。)

 

You may be wondering what happens if you want to continue playing "background media" even when the user leaves your activity, much in the same way that the built-in Music application behaves. In this case, what you need is a MediaPlayer controlled by a Service, as discussed in Using a Service with MediaPlayer.

 

你可能疑惑发生什么,如果你想继续播放“后台(注:背景)媒体”,即便当用户离开你的活动,使用与内建的Music(注:音乐)应用程序的行为非常相同的方式。在这种情况下,你所需要的是被一个Service控制的一个MediaPlayer,正如在使用带MediaPlayer的服务中所讨论的那样。

 

-------------------------------

 

Using a Service with MediaPlayer

 

使用带MediaPlayer的服务

 

If you want your media to play in the background even when your application is not onscreen—that is, you want it to continue playing while the user is interacting with other applications—then you must start a Service and control the MediaPlayer instance from there. You should be careful about this setup, because the user and the system have expectations about how an application running a background service should interact with the rest of the system. If your application does not fulfil those expectations, the user may have a bad experience. This section describes the main issues that you should be aware of and offers suggestions about how to approach them.

 

如果你想你的媒体在后台中播放,甚至当你的应用程序不在屏幕上——就是说,你想它在用户与其它应用程序交互时继续播放——那么你必须启动一个Service并且从那里控制该MediaPlayer实例。你应该小心这个配置,因为用户和系统拥有对关于一个运行后台服务的应用程序应该如何与系统的其它部分交互的期待。如果你的应用程序不履行那些期待,用户可能有一个不好的体验。这个章节描述你应该意识到的主要问题以及提出关于如何接近(注:达到)它们的建议。

 

Running asynchronously

 

异步地运行

 

First of all, like an Activity, all work in a Service is done in a single thread by default—in fact, if you're running an activity and a service from the same application, they use the same thread (the "main thread") by default. Therefore, services need to process incoming intents quickly and never perform lengthy computations when responding to them. If any heavy work or blocking calls are expected, you must do those tasks asynchronously: either from another thread you implement yourself, or using the framework's many facilities for asynchronous processing.

 

首先,像一个Activity,所有在一个Service中的工作默认在一个单线程中完成——事实上,如果你正在从相同的应用程序中运行一个活动和一个服务,它们默认使用相同的线程(“主线程”)。因此,服务需要快速地处理输入意图并且在响应它们时从不执行冗长的计算。如果期待任意繁重的工作或阻塞的调用,你必须异步地执行那些任务:或者从你自己实现的另一个线程中执行,或者使用框架的许多设施用于异步处理。

 

For instance, when using a MediaPlayer from your main thread, you should call prepareAsync() rather than prepare(), and implement a MediaPlayer.OnPreparedListener in order to be notified when the preparation is complete and you can start playing. For example:

 

例如,当从你的主线程中使用一个MediaPlayer时,你应该调用prepareAsync()而非prepare(),并且实现一个MediaPlayer.OnPreparedListener以便当准备完成时被通知,而你可以开始播放。例如:

 

-------------------------------

 

public class MyService extends Service implements MediaPlayer.OnPreparedListener {

    private static final ACTION_PLAY = "com.example.action.PLAY";

    MediaPlayer mMediaPlayer = null;

 

    public int onStartCommand(Intent intent, int flags, int startId) {

        ...

        if (intent.getAction().equals(ACTION_PLAY)) {

            mMediaPlayer = ... // initialize it here 在这里初始化它

            mMediaPlayer.setOnPreparedListener(this);

            mMediaPlayer.prepareAsync(); // prepare async to not block main thread 准备同步以不阻塞主线程

        }

    }

 

    /** Called when MediaPlayer is ready */

    /** 当MediaPlayer准备好时被调用 */

    public void onPrepared(MediaPlayer player) {

        player.start();

    }

}

 

-------------------------------

 

Handling asynchronous errors

 

处理异步错误

 

On synchronous operations, errors would normally be signaled with an exception or an error code, but whenever you use asynchronous resources, you should make sure your application is notified of errors appropriately. In the case of a MediaPlayer, you can accomplish this by implementing a MediaPlayer.OnErrorListener and setting it in your MediaPlayer instance:

 

在同步操作上,错误将通常地用一个异常或一个错误码来通知,但每当你使用异步资源时,你应该确保你的应用程序被正确地通知错误。在MediaPlayer的情况下,你可以通过实现一个MediaPlayer.OnErrorListener并且在你的MediaPlayer实例中设置它来做到这点。

 

-------------------------------

 

public class MyService extends Service implements MediaPlayer.OnErrorListener {

    MediaPlayer mMediaPlayer;

 

    public void initMediaPlayer() {

        // ...initialize the MediaPlayer here...

// ...在这里初始化MediaPlayer...

        mMediaPlayer.setOnErrorListener(this);

    }

 

    @Override

    public boolean onError(MediaPlayer mp, int what, int extra) {

        // ... react appropriately ...

        // The MediaPlayer has moved to the Error state, must be reset!

        // ...合适地反应...

        // MediaPlayer已经被移动到错误状态,必须被重置!

    }

}

 

-------------------------------

 

It's important to remember that when an error occurs, the MediaPlayer moves to the Error state (see the documentation for the MediaPlayer class for the full state diagram) and you must reset it before you can use it again.

 

重要的是记住当一个错误发生时,MediaPlayer移动到Error状态(参见MediaPlayer类的文档以获得完整状态图)并且在你可以使用它之前你必须重置它。

 

Using wake locks

 

使用醒锁

 

When designing applications that play media in the background, the device may go to sleep while your service is running. Because the Android system tries to conserve battery while the device is sleeping, the system tries to shut off any of the phone's features that are not necessary, including the CPU and the WiFi hardware. However, if your service is playing or streaming music, you want to prevent the system from interfering with your playback.

 

当设计在后台中播放媒体的应用程序时,当你的服务正在运行时该设备可能进入休眠。因为当设备正在睡眠时Android系统尝试节约电量,系统尝试关闭任意不必需的电话特性,包括CPU和WiFi设备。然而,如果你的服务正在运行或流传输音乐,你会想阻止系统干扰你的回放。

 

In order to ensure that your service continues to run under those conditions, you have to use "wake locks." A wake lock is a way to signal to the system that your application is using some feature that should stay available even if the phone is idle.

 

为了确保你的服务继续运行在那些条件下,你不得不使用“醒锁”。醒锁是通知系统你的应用程序正在使用一些即便电话在空闲时也应该保持可用的特性的一种方式。

 

-------------------------------

 

Notice: You should always use wake locks sparingly and hold them only for as long as truly necessary, because they significantly reduce the battery life of the device.

 

注意:你应该总是谨慎地使用醒锁并且持有它们只是持续尽真的有必要地长的时间,因为它们显著地缩短设备的电池寿命(注:电池续航)。

 

-------------------------------

 

To ensure that the CPU continues running while your MediaPlayer is playing, call the setWakeMode() method when initializing your MediaPlayer. Once you do, the MediaPlayer holds the specified lock while playing and releases the lock when paused or stopped:

 

为了确保当你的MediaPlayer正在播放时CPU继续运行,请在初始化你的MediaPlayer时调用setWakeMode()方法。一旦你这样做,MediaPlayer在播放的时候持有指定的锁并且在暂停或停止时释放锁:

 

-------------------------------

 

mMediaPlayer = new MediaPlayer();

// ... other initialization here ...

// ...其它初始化在这里...

mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

 

-------------------------------

 

However, the wake lock acquired in this example guarantees only that the CPU remains awake. If you are streaming media over the network and you are using Wi-Fi, you probably want to hold a WifiLock as well, which you must acquire and release manually. So, when you start preparing the MediaPlayer with the remote URL, you should create and acquire the Wi-Fi lock. For example:

 

然而,在这个示例中获得的醒锁只保证CPU维持唤醒。如果你正在通过网络流传输媒体和你正在使用Wi-Fi,你还很可能想持有一个Wifi锁(注:WifiLock),你必须手动地获得和释放它。所以,当你开始用远程URL来准备MediaPlayer,你应该创建和取得Wi-Fi锁。例如:

 

-------------------------------

 

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))

    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

 

wifiLock.acquire();

 

-------------------------------

 

When you pause or stop your media, or when you no longer need the network, you should release the lock:

 

当你暂停或停止你的媒体时,或者当你不再需要网络时,你应该释放该锁:

 

-------------------------------

 

wifiLock.release();

 

-------------------------------

 

Running as a foreground service

 

运行作为一个前台服务

 

Services are often used for performing background tasks, such as fetching emails, synchronizing data, downloading content, amongst other possibilities. In these cases, the user is not actively aware of the service's execution, and probably wouldn't even notice if some of these services were interrupted and later restarted.

 

服务通常被用于执行后台任务,诸如取出电子邮件,同步数据,下载内容,在其它可能性之间。在这些情况下,用户不主动地意识到服务的执行,并且甚至很可能不注意到这些服务中的一些被中断和稍后被重启。

 

But consider the case of a service that is playing music. Clearly this is a service that the user is actively aware of and the experience would be severely affected by any interruptions. Additionally, it's a service that the user will likely wish to interact with during its execution. In this case, the service should run as a "foreground service." A foreground service holds a higher level of importance within the system—the system will almost never kill the service, because it is of immediate importance to the user. When running in the foreground, the service also must provide a status bar notification to ensure that users are aware of the running service and allow them to open an activity that can interact with the service.

 

但考虑正在播放音乐的服务的情况。无疑这是用户实际上意识到的一个服务,而体验将被任意中断严重地影响。另外,这是一个用户将可能希望在它执行期间与它交互的服务。在这种情况下,服务应该运行作为一个“前台服务”。前台服务持有系统中一个较高等级的重要度——系统将几乎从不杀死该服务,因为它对于用户来说有直接的重要性。当运行在前台中时,服务也必须提供一个状态栏通知以确保用户意识到正在运行的服务并且允许它们打开一个可以与服务交互的活动。

 

In order to turn your service into a foreground service, you must create a Notification for the status bar and call startForeground() from the Service. For example:

 

为了把你的服务转换为一个前台服务,你必须为该状态栏创建一个Notification并且从该Service中调用startForeground()。例如:

 

-------------------------------

 

String songName;

// assign the song name to songName

// 赋予歌名给songName

PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,

                new Intent(getApplicationContext(), MainActivity.class),

                PendingIntent.FLAG_UPDATE_CURRENT);

Notification notification = new Notification();

notification.tickerText = text;

notification.icon = R.drawable.play0;

notification.flags |= Notification.FLAG_ONGOING_EVENT;

notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",

                "Playing: " + songName, pi);

startForeground(NOTIFICATION_ID, notification);

 

-------------------------------

 

While your service is running in the foreground, the notification you configured is visible in the notification area of the device. If the user selects the notification, the system invokes the PendingIntent you supplied. In the example above, it opens an activity (MainActivity).

 

当你的服务正在运行于前台中时,你配置的通知在设备的通知区域中是可见的。如果用户选择该通知,系统调用你提供的PendingIntent。在上面的示例中,它打开一个活动(MainActivity)。

 

Figure 1 shows how your notification appears to the user:

 

图1展示你的通知如何显示给用户:

 

-------------------------------

 

(图略:

左图:主页屏,左上角为播放图标

右图:通知屏,正在运行,音乐播放器示例

 

Figure 1. Screenshots of a foreground service's notification, showing the notification icon in the status bar (left) and the expanded view (right).

 

图1. 一个前台服务的通知的截屏,在状态栏中显示通知图标(左)和被展开的视图(右)

 

-------------------------------

 

You should only hold on to the "foreground service" status while your service is actually performing something the user is actively aware of. Once that is no longer true, you should release it by calling stopForeground():

 

你应该只是一直维持“前台服务”的状态,当你的服务实际上正在执行用户实际上意识到的一些事情时。一旦不再是那样,你应该通过调用stopForeground()释放它:

 

-------------------------------

 

stopForeground(true);

 

-------------------------------

 

For more information, see the documentation about Services and Status Bar Notifications.

 

想获得更多信息,请参见关于服务和状态栏通知的文档。

 

Handling audio focus

 

处理音频焦点

 

Even though only one activity can run at any given time, Android is a multi-tasking environment. This poses a particular challenge to applications that use audio, because there is only one audio output and there may be several media services competing for its use. Before Android 2.2, there was no built-in mechanism to address this issue, which could in some cases lead to a bad user experience. For example, when a user is listening to music and another application needs to notify the user of something very important, the user might not hear the notification tone due to the loud music. Starting with Android 2.2, the platform offers a way for applications to negotiate their use of the device's audio output. This mechanism is called Audio Focus.

 

即便只有一个活动可以运行于任意给定的时刻,然而Android是一个多任务环境。这对使用音频的应用程序造成一个特殊的挑战,因为只有一个音频输出并且可能有几个音频服务争夺它的使用权。在Android 2.2之前,没有内建机制来解决这个问题,这可能在一些情况下导致一个不好的用户体验。例如,当一位用户正在听音乐而另一个应用程序需要通知用户一些非常重要的事情时,用户可能因为响亮的音乐而听不到通知铃声。从Android 2.2开始,平台为应用程序提供一种方式协商它们对设备音频输出的使用。这个机制被称为音频焦点。

 

When your application needs to output audio such as music or a notification, you should always request audio focus. Once it has focus, it can use the sound output freely, but it should always listen for focus changes. If it is notified that it has lost the audio focus, it should immediately either kill the audio or lower it to a quiet level (known as "ducking"—there is a flag that indicates which one is appropriate) and only resume loud playback after it receives focus again.

 

当你的应用程序需要输出音频诸如音乐或通知,你应该总是请求音频焦点。一旦它拥有焦点,它可以自由地使用音频输出,但它应该总是监听焦点改变。如果它被通知它已经失去音频焦点,它应该立刻杀死音频或降低它到一个安静级别(被称为“闪避”——有一个标志指示哪一个是合适的)(注:ducking,闪避,也被译作潜水,因为duck作动词时有躲避,回避,潜入的意思)并且在它再次接收焦点后才恢复响亮的回放。

 

Audio Focus is cooperative in nature. That is, applications are expected (and highly encouraged) to comply with the audio focus guidelines, but the rules are not enforced by the system. If an application wants to play loud music even after losing audio focus, nothing in the system will prevent that. However, the user is more likely to have a bad experience and will be more likely to uninstall the misbehaving application.

 

音频焦点本质上是协作的。就是说,应用程序被期待(和被高度鼓励)遵循音频焦点指南,但规则不是被系统强制实施的。如果一个应用程序想播放响亮的音乐即便在失去音频焦点后,那么系统中没有东西能阻止它。然而,用户更可能拥有一个不好的体验并将更可能卸载那个行为不端的应用程序。

 

To request audio focus, you must call requestAudioFocus() from the AudioManager, as the example below demonstrates:

 

为了请求音频焦点,你必须从该AudioManager中调用requestAudioFocus(),正如以下示例所示:

 

-------------------------------

 

AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,

    AudioManager.AUDIOFOCUS_GAIN);

 

if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {

    // could not get audio focus.

    // 不能获得音频焦点

}

 

-------------------------------

 

The first parameter to requestAudioFocus() is an AudioManager.OnAudioFocusChangeListener, whose onAudioFocusChange() method is called whenever there is a change in audio focus. Therefore, you should also implement this interface on your service and activities. For example:

 

传给requestAudioFocus()的第一个参数是一个AudioManager.OnAudioFocusChangeListener,它的onAudioFocusChange()方法被调用,每当在音频焦点中存在一次改变。因此,你还应该在你的服务和多个活动上实现这个接口。例如:

 

-------------------------------

 

class MyService extends Service

                implements AudioManager.OnAudioFocusChangeListener {

    // ....

    public void onAudioFocusChange(int focusChange) {

        // Do something based on focus change...

        // 做一些事情,基于焦点改变

    }

}

 

-------------------------------

 

The focusChange parameter tells you how the audio focus has changed, and can be one of the following values (they are all constants defined in AudioManager):

 

focusChange参数告诉你音频焦点曾经如何改变,并且可以是以下值之一(它们是定义在AudioManager中的所有常量):

 

* AUDIOFOCUS_GAIN: You have gained the audio focus.

 

* AUDIOFOCUS_GAIN:你已经获得音频焦点。

 

* AUDIOFOCUS_LOSS: You have lost the audio focus for a presumably long time. You must stop all audio playback. Because you should expect not to have focus back for a long time, this would be a good place to clean up your resources as much as possible. For example, you should release the MediaPlayer.

 

* AUDIOFOCUS_LOSS:你已经失去音频焦点一段大致上长的时间。你必须停止所有音频回放。因为你应该期待不让焦点回来一段长时间,这应该是一个好的地方以尽可能多地清除你的资源。例如,你应该释放MediaPlayer。

 

* AUDIOFOCUS_LOSS_TRANSIENT: You have temporarily lost audio focus, but should receive it back shortly. You must stop all audio playback, but you can keep your resources because you will probably get focus back shortly.

 

* AUDIOFOCUS_LOSS_TRANSIENT:你已经临时地丢失音频焦点,但应该不久后接收它回来。你必须停止所有音频回放,但你可以保持你的资源因为你将很可能在不久后获得焦点回来。

 

* AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: You have temporarily lost audio focus, but you are allowed to continue to play audio quietly (at a low volume) instead of killing audio completely.

 

* AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你曾经临时地丢失音频焦点,但你被允许安静地继续播放音频(以一个低音量)而非完全地杀死音频。

 

Here is an example implementation:

 

这里是一个示例实现:

 

-------------------------------

 

public void onAudioFocusChange(int focusChange) {

    switch (focusChange) {

        case AudioManager.AUDIOFOCUS_GAIN:

            // resume playback

            // 恢复回放

            if (mMediaPlayer == null) initMediaPlayer();

            else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();

            mMediaPlayer.setVolume(1.0f, 1.0f);

            break;

 

        case AudioManager.AUDIOFOCUS_LOSS:

            // Lost focus for an unbounded amount of time: stop playback and release media player

            // 失去焦点一段无限长的时间:停止回放并释放媒体播放器

            if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();

            mMediaPlayer.release();

            mMediaPlayer = null;

            break;

 

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

            // Lost focus for a short time, but we have to stop

            // playback. We don't release the media player because playback

            // is likely to resume

            // 失去焦点一段短的事件,但我们不得不停止回放。

            // 我们不释放媒体播放器,因为回放可能要恢复

            if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();

            break;

 

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

            // Lost focus for a short time, but it's ok to keep playing

            // at an attenuated level

            // 失去焦点一段短的时间,但在一个衰减的级别上保持播放是可以的

            if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);

            break;

    }

}

 

-------------------------------

 

Keep in mind that the audio focus APIs are available only with API level 8 (Android 2.2) and above, so if you want to support previous versions of Android, you should adopt a backward compatibility strategy that allows you to use this feature if available, and fall back seamlessly if not.

 

请谨记音频焦点API仅对于API级别8(Android 2.2)和以上是可用的,所以如果你想支持之前版本的Android,你应该采取一个向后兼容性策略,它允许你使用这个特性如果可用的话,并且无缝地回退如果不可用。

 

You can achieve backward compatibility either by calling the audio focus methods by reflection or by implementing all the audio focus features in a separate class (say, AudioFocusHelper). Here is an example of such a class:

 

你可以实现向后兼容性,通过用反射调用音频焦点的方法或者通过在一个单独类中实现所有音频焦点特性(例如,AudioFocusHelper)。这里是这个类的一个示例:

 

-------------------------------

 

public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {

    AudioManager mAudioManager;

 

    // other fields here, you'll probably hold a reference to an interface

    // that you can use to communicate the focus changes to your Service

    // 其它域在这里,你将好呢可能持有一个指向一个接口的引用,

    // 你可以使用它把焦点的改变通信给你的Service

 

    public AudioFocusHelper(Context ctx, /* other arguments here */ /*其它参数在这里*/) {

        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        // ...

    }

 

    public boolean requestFocus() {

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==

            mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,

            AudioManager.AUDIOFOCUS_GAIN);

    }

 

    public boolean abandonFocus() {

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==

            mAudioManager.abandonAudioFocus(this);

    }

 

    @Override

    public void onAudioFocusChange(int focusChange) {

        // let your service know about the focus change

        // 让你的服务知道焦点改变

    }

}

 

-------------------------------

 

You can create an instance of AudioFocusHelper class only if you detect that the system is running API level 8 or above. For example:

 

你可以创建AudioFocusHelper类的一个实例,仅当你检测到系统正在运行API级别8或以上。例如:

 

-------------------------------

 

if (android.os.Build.VERSION.SDK_INT >= 8) {

    mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);

} else {

    mAudioFocusHelper = null;

}

 

-------------------------------

 

Performing cleanup

 

执行清除

 

As mentioned earlier, a MediaPlayer object can consume a significant amount of system resources, so you should keep it only for as long as you need and call release() when you are done with it. It's important to call this cleanup method explicitly rather than rely on system garbage collection because it might take some time before the garbage collector reclaims the MediaPlayer, as it's only sensitive to memory needs and not to shortage of other media-related resources. So, in the case when you're using a service, you should always override the onDestroy() method to make sure you are releasing the MediaPlayer:

 

正如较早前提及的,一个MediaPlayer对象可能消耗显著数量的系统资源,所以你应该保持它只花如你需那么长的时间,并且在你处理完它时调用release()。重要的是显式地调用这个清除方法而非依赖于系统的垃圾回收,因为在垃圾回收器回收该MediaPlayer之前可能要花一些时间。所以,在当你正在使用一个服务的时候的情况下,你应该总是覆盖onDestroy()方法以确保你释放MediaPlayer:

 

-------------------------------

 

public class MyService extends Service {

   MediaPlayer mMediaPlayer;

   // ...

 

   @Override

   public void onDestroy() {

       if (mMediaPlayer != null) mMediaPlayer.release();

   }

}

 

-------------------------------

 

You should always look for other opportunities to release your MediaPlayer as well, apart from releasing it when being shut down. For example, if you expect not to be able to play media for an extended period of time (after losing audio focus, for example), you should definitely release your existing MediaPlayer and create it again later. On the other hand, if you only expect to stop playback for a very short time, you should probably hold on to your MediaPlayer to avoid the overhead of creating and preparing it again.

 

你还应该总是寻找其它机会来释放你的MediaPlayer,除了在被关闭时释放它。例如,如果你期待不能播放媒体一段被延长的时间段(例如,在丢失音频焦点后)。你应该明确地释放你的现存MediaPlayer并且稍后再次创建它。另一方面。如果你只期待停止回放一段非常短的时间,你也许应该继续持有(注:坚持)MediaPlayer以避免再次创建和准备它的开销。

 

Handling the AUDIO_BECOMING_NOISY Intent

 

处理AUDIO_BECOMING_NOISY意图

 

Many well-written applications that play audio automatically stop playback when an event occurs that causes the audio to become noisy (ouput through external speakers). For instance, this might happen when a user is listening to music through headphones and accidentally disconnects the headphones from the device. However, this behavior does not happen automatically. If you don't implement this feature, audio plays out of the device's external speakers, which might not be what the user wants.

 

许多编写良好的播放音频的应用程序自动地停止回放,当一个事件发生,它导致音频变得吵杂(通过外部扬声器输出)。例如,这可能发生当一位用户正在通过耳机听音乐并且偶然从设备中断开耳机的连接。然而,这个行为不自动地发生。如果你不实现这个特性,音频会从设备的外部扬声器中播放出来,这可能不是用户所想要的。

 

You can ensure your app stops playing music in these situations by handling the ACTION_AUDIO_BECOMING_NOISY intent, for which you can register a receiver by adding the following to your manifest:

 

你可以确保你的应用在这些情况下通过处理ACTION_AUDIO_BECOMING_NOISY意图来停止播放音乐,因此你可以通过添加以下内容到你的清单中来注册一个接收器。

 

-------------------------------

 

<receiver android:name=".MusicIntentReceiver">

   <intent-filter>

      <action android:name="android.media.AUDIO_BECOMING_NOISY" />

   </intent-filter>

</receiver>

 

-------------------------------

 

This registers the MusicIntentReceiver class as a broadcast receiver for that intent. You should then implement this class:

 

它注册MusicIntentReceiver类作为那个意图的一个广播接收器。然后你应该实现这个类:

 

-------------------------------

 

public class MusicIntentReceiver implements android.content.BroadcastReceiver {

   @Override

   public void onReceive(Context ctx, Intent intent) {

      if (intent.getAction().equals(

                    android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {

          // signal your service to stop playback

          // (via an Intent, for instance)

          // 通知你的服务停止回放

          // (例如,通过一个Intent)

      }

   }

}

 

-------------------------------

 

-------------------------------

 

Retrieving Media from a Content Resolver

 

从一个内容解析器中取出媒体

 

Another feature that may be useful in a media player application is the ability to retrieve music that the user has on the device. You can do that by querying the ContentResolver for external media:

 

在一个媒体播放器应用程序中可能有用的另一个特性是取出在设备上用户拥有的音乐的能力。你可以通过向ContentResolver查询外部媒体来做那件事:

 

-------------------------------

 

ContentResolver contentResolver = getContentResolver();

Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

Cursor cursor = contentResolver.query(uri, null, null, null, null);

if (cursor == null) {

    // query failed, handle error.

    // 查询失败,处理错误。

} else if (!cursor.moveToFirst()) {

    // no media on the device

    // 设备上没有媒体

} else {

    int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);

    int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);

    do {

       long thisId = cursor.getLong(idColumn);

       String thisTitle = cursor.getString(titleColumn);

       // ...process entry...

       // ...处理条目...

    } while (cursor.moveToNext());

}

 

-------------------------------

 

To use this with the MediaPlayer, you can do this:

 

为了用MediaPlayer来使用它,你可以做这件事:

 

-------------------------------

 

long id = /* retrieve it from somewhere */; /*从某些地方取出它*/

Uri contentUri = ContentUris.withAppendedId(

        android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);

 

mMediaPlayer = new MediaPlayer();

mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);

mMediaPlayer.setDataSource(getApplicationContext(), contentUri);

 

// ...prepare and start...

// ...准备并开始...

 

-------------------------------

 

Except as noted, this content is licensed under Apache 2.0. For details and restrictions, see the Content License.

 

除特别说明外,本文在Apache 2.0下许可。细节和限制请参考内容许可证。

 

Android 4.0 r1 - 16 Apr 2012 17:06

 

-------------------------------

 

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.

 

此页部分内容,是基于Android开源项目所创建和共享的工作,并且根据知识共享2.5署名许可证描述的条款来使用的修改版。

 

(本人翻译质量欠佳,请以官方最新内容为准,或者参考其它翻译版本:

* ソフトウェア技術ドキュメントを勝手に翻訳

http://www.techdoctranslator.com/android

* Ley's Blog

http://leybreeze.com/blog/

* 农民伯伯

http://www.cnblogs.com/over140/

* Android中文翻译组

http://androidbox.sinaapp.com/


分享到:
评论

相关推荐

    FlashMediaServer3翻译文档

    4. **记录与回放**:FMS3可以记录流媒体内容,便于后续点播或分析。同时,它的时间平移功能允许用户在直播中回放内容。 5. **内容保护**:通过数字版权管理(DRM)系统,FMS3可以帮助内容提供商保护其知识产权,...

    多媒体形式简介.docx

    虚拟现实是一个 VOD 系统主要由三部分构成:拟现实是从英文 Virtual Reality 一词翻译过来的,Virtual 就是虚假的意思,Reality 就是真实的意思,合并起来就是虚拟服务端系统现实,也就是说本来没有的事物和环境,...

    DirectShowSDK中文翻译

    - **媒体数据回放**:支持广泛的媒体文件格式回放。 - **集成DirectX技术**:与DirectDraw、DirectSound等技术集成,支持高级应用如DVD播放、视频非线性编辑等。 - **开放式架构**:允许开发者创建自定义组件以扩展...

    大型企业多媒体中控方案

    3. **同声翻译系统**:支持多达22种语言的同声传译,具备清晰的音质和图像同步功能,且结构模块化,安装和操作简便。 4. **环境控制功能**:除了设备控制外,还提供了对室内环境的控制能力,如灯光亮度调整、窗帘...

    directshow_SDK开发笔记(翻译).pdf

    DirectShow主要用于多媒体数据的捕捉和回放,支持多种媒体格式,包括但不限于ASF、MPEG、AVI、DV、MP3、Wave等。此外,DirectShow还整合了DirectX的其他组件技术,例如DirectDraw和DirectSound,从而支持DVD播放、...

    多媒体会议系统典型施工组织设计及对策.doc

    建设目标可能包括建立一个能支持远程视频会议、实时数据共享、电子白板功能、录制与回放、多语言翻译等全方位的沟通平台。 1.2. 系统设计技术标准及规范 遵循的技术标准可能包括H.323、SIP等视频通信协议,AES音频...

    rfc2326.doc(RTSP1.0)

    简而言之,RTSP 是一种控制工具,让服务器能够根据客户端的指令来播放、暂停、快进或回放多媒体内容。 1.2 应用场景 RTSP 适用于多种场景,包括在线直播、点播视频服务以及企业级视频会议等。它允许用户选择不同的...

    Moboplayer 1.3.277

    MoboPlayer 是运行在移动设备上,为音频和视频提供高质量回放的播放软件。它可以让你在移动设备上播放几乎所有的多媒体格式,将你的数字生活体验提升到新的境界。 目前 MoboPlayer 可以运行在几乎所有的 Android ...

    佳的美 驱动

    5. **录制与回放**:具备录像功能,可以将电视节目录制下来,便于日后回放。 6. **网络应用**:支持网页浏览、社交媒体访问、在线视频平台的应用程序,让用户在大屏幕上享受互联网服务。 7. **外设支持**:兼容...

    RTP,RTSP协议中文版

    4. **重定向和记录**:RTSP服务器可以根据需要将请求重定向到其他服务器,或者记录媒体流供以后回放。 在学习RTP和RTSP协议的过程中,通常会参考IETF发布的RFC文档,如RFC3550(RTP规范)和RFC2326(RTSP规范)。...

    GDI+ SDK参考(翻译版本)

    ### GDI+ SDK 参考(翻译版本) #### 序言 **目标** Microsoft Windows GDI+ 是一个专为 C/C++ 开发者设计的基于类的应用程序编程接口(API)。该 API 的主要目的是使开发者能够更加高效地创建高质量的图形用户...

    极域2007破解版

    它代表着一种全新的教学方式,利用一套软件,在现有的电脑网络设备上,实现教师机对学生机的广播、监控、屏幕录制、屏幕回放、语音教学等操作来统一地进行管理与监控,辅助学生完成电脑软件的学习、使用。...

    达芬奇界面中英文对照

    - 软件界面中的主要功能区,包含文件操作、编辑工具、视图选项、标记管理、回放控制、色彩校正、节点编辑及帮助文档等。 #### 二、文件与编辑 - **文件**: File - 包含“新建项目”、“新建夹子”等菜单项,用于...

    2021计算机程序设计-计算机程序设计综合练习(精选试题).doc

    2. 数字媒体与编码:数字形式的媒体主要特征是通过计算机网络用二进制编码表示文本、图片等信息。二进制编码是计算机处理信息的基础,其他进制编码(八进制、十进制、十六进制)都是二进制的扩展,但在计算机内部,...

    扬州市电子政务视频会议系统

    例如,会议主持者可以对参会者进行控制,共享电子白板、程序、文字和实录回放,且系统支持桌面会议和一对一、一对多的会议模式。 接口功能确保了视频会议系统与城市应急指挥系统及其他电子政务应用,如GIS系统的...

    ONVIF2.0中文协议原版-协议

    "ONVIF2.0中文协议原版-协议"文档是ONVIF组织发布的最新版本,针对中文用户进行了本地化翻译,方便国内开发者理解和应用。 ONVIF协议主要包含以下几个核心部分: 1. **设备发现**:ONVIF定义了一套基于UPnP...

    什么是hifi音响 HiFi音箱与AV音箱有哪些区别【详解】.docx

    Hifi音响,全称为High Fidelity,中文翻译为“高保真音响”,是指一种能够尽可能真实地还原声音信号原有特性的音响系统。它旨在提供卓越的音质体验,让听众感受到音乐的每一个细节和情感。Hifi音响系统通常包括音源...

    sparkweb汉化版

    7. `locale` 文件夹:这个文件夹很可能包含了不同语言的本地化资源,尽管标题提到是汉化版,但通常这样的文件夹会包含多国语言的翻译,以便适应全球用户。 总的来说,【SparkWeb汉化版】是为了解决原版SparkWeb的...

    ONVIF2.0协议中文珍藏版.rar

    2. 流媒体服务:ONVIF2.0支持实时视频和音频流的传输,包括RTSP(Real-Time Streaming Protocol)协议,允许用户在不同的设备之间获取和播放媒体流。同时,协议还规定了编码、解码、分辨率和帧率等参数的设置。 3. ...

Global site tag (gtag.js) - Google Analytics