基于网络音频的Android播放程序简单示例

简介: 基于网络音频的Android播放程序简单示例

随着发布MP3文件、播客以及流式音频变得越来越受欢迎,构建可以利用这些服务的音频播放程序的需求也越来越强烈。幸运的是,Android拥有丰富的功能用于处理网络上存在的各种类型的音频。


1.基于HTTP音频播放


这是最简单的的情况,仅仅播放在线的、可通过HTTP对其进行访问的音频文件。比如http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3


但是这里和通常示例化MediaPlayer的方式不同,首先使用的是MediaPlayer的无参构造函数来实例化对象,接着,调用其setDataSource方法,传入想要播放的音频的HTTP位置,随后我们调用prepare方法和start方法。

mediaPlayer = new MediaPlayer();
try {
  mediaPlayer
  .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
  mediaPlayer.prepare();
  mediaPlayer.start();
} catch (IOException e) {
  Log.v("AUDIOHTTPPLAYER", e.getMessage());
}

但是,在应用程序加载到播放音频之间有一个明显的滞后时间。延迟的长度取决于用于构建电话Internet连接的数据网络的速度。如果详细分析的话,可以找到是在调用prepare方法和start方法之间发生了这样的延迟。在运行prepare期间,MediaPlayer将填充一个缓冲区,因为即使网络速度缓慢也能平稳的播放音频。当这么操作时,prepare方法实际上发生了阻塞。这意味着应用程序可能要等到prepare方法完成之后才会响应。幸运的是,有一种方法可以解决这个问题,即prepareAsync方法。该方法会立即返回,并在后台执行缓冲和其他工作,从而允许应用程序继续运行。


完整示例代码如下:

public class AudioHTTPPlayer extends Activity implements OnClickListener,
    OnErrorListener, OnCompletionListener, OnBufferingUpdateListener,
    OnPreparedListener
{
  /** Called when the activity is first created. */
  MediaPlayer mediaPlayer;
  Button stopButton, startButton;
  TextView statusTextView, bufferValueTextView;
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    stopButton = (Button) findViewById(R.id.EndButton);
    startButton = (Button) findViewById(R.id.StartButton);
    startButton.setOnClickListener(this);
    stopButton.setOnClickListener(this);
    startButton.setEnabled(false);
    stopButton.setEnabled(false);
    bufferValueTextView = (TextView) findViewById(R.id.BufferValueTextView);
    statusTextView = (TextView) findViewById(R.id.StatusDisplayTextView);
    statusTextView.setText("onCreate");
    mediaPlayer = new MediaPlayer();
    mediaPlayer.setOnCompletionListener(this);
    mediaPlayer.setOnErrorListener(this);
    mediaPlayer.setOnBufferingUpdateListener(this);
    mediaPlayer.setOnPreparedListener(this);
    statusTextView.setText("MediaPlayer created");
    try
    {
      mediaPlayer
          .setDataSource("http://www.mobvcasting.com/android/audio/goodmorningandroid.mp3");
      // mediaPlayer.prepare();
      // mediaPlayer.start();
      statusTextView.setText("setDataSource done");
      statusTextView.setText("calling prepareAsync");
      mediaPlayer.prepareAsync();// 开始在后台缓冲音频文件并返回
    } catch (IOException e)
    {
      Log.v("AUDIOHTTPPLAYER", e.getMessage());
    }
  }
  @Override
  public void onPrepared(MediaPlayer mp)
  {
    // TODO Auto-generated method stub
//  当完成prepareAsync方法时,将调用活动的onPrepared方法
    statusTextView.setText("onPrepared called");
    startButton.setEnabled(true);
  }
  @Override
  public void onBufferingUpdate(MediaPlayer mp, int percent)
  {
    // TODO Auto-generated method stub
//    当MediaPlayer正在缓冲时,将调用活动的onBufferingUpdate方法
    bufferValueTextView.setText(""+percent+"%");
  }
  @Override
  public void onCompletion(MediaPlayer mp)
  {
    // TODO Auto-generated method stub
    statusTextView.setText("onCompletion called");
    stopButton.setEnabled(false);
    startButton.setEnabled(true);
  }
  @Override
  public boolean onError(MediaPlayer mp, int what, int extra)
  {
    // TODO Auto-generated method stub
    switch (what)
    {
      case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
        statusTextView
            .setText("MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK"
                + extra);
        break;
      case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
        statusTextView.setText("MEDIA_ERROR_SERVER_DIED" + extra);
        break;
      case MediaPlayer.MEDIA_ERROR_UNKNOWN:
        statusTextView.setText("MEDIA_ERROR_UNKNOWN" + extra);
        break;
    }
    return false;
  }
  @Override
  public void onClick(View v)
  {
    // TODO Auto-generated method stub
    if (v == stopButton)
    {
      mediaPlayer.pause();
      statusTextView.setText("pause called");
      startButton.setEnabled(true);
    } else if (v == startButton)
    {
      mediaPlayer.start();
      statusTextView.setText("start called");
      startButton.setEnabled(false);
      stopButton.setEnabled(true);
    }
  }
}

如上所示,MediaPlayer有良好的功能集,用来处理HTTP在线获取的音频文件。

2.基于HTTP的流式音频


在线音频常用的在线传输方法之一是通过HTTP流。有多种流方法属于HTTP流方法的分支,包括服务器推送,这在历史上一直用于在浏览器中刷新网络摄像头图像显示;以及一系列其他新方法。而联机广播事实上的标准则是ICY协议,其扩展了HTTP协议,目前大量的服务器和播放软件产品都支持这个协议。


幸运的是,android上的MediaPlayer支持播放ICY流,而无须开发人员费力地实现它。


然后,Internet广播电台并不直接公布它们的音频流的URL。这么做是因为浏览器通常不支持ICY流,而是需要一个辅助应用程序或插件来播放流。为了知道要打开的是一个辅助应用程序,Internet广播电台会 传递一个特定的MIME类型的中间文件,其中包含一个指向实际在线流的指针。在使用ICY流的情况下,这通常是一个PLS文件或一个M3U文件


PLS文件:是一种多媒体播放列表文件,其MIME类型是“audio/x-scpls”


M3U文件:一个存储多媒体播放列表的文件,但是采用一种更基本的格式。它的MIME类型为“audio/x-mpegurl”。


例如M3U文件的内容如下,其指向了一个虚假的在线流

#EXTM3U
#EXTINF:0,Live Stream Name
http://www.nostreamhere.org:8000/

第一行的#EXTM3U是必须的,其指定下面是一个扩展的M3U文件,其中可以包含额外的信息。可以在播放列表条目的上一行指定额外信息,其以#EXTINF:开始,随后是以秒为单位的持续时间和逗号,然后是媒体的名称。

M3U文件可以同时包含多个条目,这些条目依次指定一个文件或流#EXTM3U#EXTINF:0,Live Stream Namehttp://www.nostreamhere.org:8000/#EXTINF:0,Other Live Stream Namehttp://www.nostreamhere.org/遗憾的是,android上的MediaPlayer不能自动分析M3U文件。因此必须我们自己分析。下面就是一个示例,分析并播放来自联机广播电台的M3U文件或在URL字段中输入的任何M3U文件。

public class HTTPAudioPlaylistPlayer extends Activity implements
    OnClickListener, OnCompletionListener, OnPreparedListener
{
  Vector playlistItems;
  Button parseBtn, playBtn, stopBtn;
  EditText editTextUrl;
  String baseURL = "";
  MediaPlayer mediaPlayer;
  int currentPlaylistItemNumber = 0;
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main2);
    parseBtn = (Button) findViewById(R.id.ParseButton);
    playBtn = (Button) findViewById(R.id.PlayButton);
    stopBtn = (Button) findViewById(R.id.StopButton);
    editTextUrl=(EditText) findViewById(R.id.EditTextURL);
    playBtn.setOnClickListener(this);
    parseBtn.setOnClickListener(this);
    stopBtn.setOnClickListener(this);
    playBtn.setEnabled(false);
    stopBtn.setEnabled(false);
    mediaPlayer = new MediaPlayer();
    mediaPlayer.setOnCompletionListener(this);
    mediaPlayer.setOnPreparedListener(this);
  }
  @Override
  public void onPrepared(MediaPlayer mp)
  {
    // TODO Auto-generated method stub
    stopBtn.setEnabled(true);
    Log.v("HTTPAUDIOPLAYLIST", "Playing");
    mediaPlayer.start();
  }
  @Override
  public void onCompletion(MediaPlayer mp)
  {
    // TODO Auto-generated method stub
    Log.v("ONCOMPLETION", "called");
    mediaPlayer.stop();
    mediaPlayer.reset();
    if (playlistItems.size() > currentPlaylistItemNumber + 1)
    {
      currentPlaylistItemNumber++;
      String path = ((PlaylistFile) playlistItems
          .get(currentPlaylistItemNumber)).getFilePath();
      try
      {
        mediaPlayer.setDataSource(path);
        mediaPlayer.prepareAsync();
      } catch (IllegalArgumentException e)
      {
        e.printStackTrace();
      } catch (IllegalStateException e)
      {
        e.printStackTrace();
      } catch (IOException e)
      {
        e.printStackTrace();
      }
    }
  }
  @Override
  public void onClick(View v)
  {
    // TODO Auto-generated method stub
    if (v == parseBtn)
    {
      // 下载由editTextUrl对象中的URL指定的M3U文件,并对它进行分析。
      // 分析的操作是选出任何表示待播放文件的行,创建一个PlaylistItem对象,
      // 然后把它添加到playlistItems容器里
      parsePlaylistFile();
    } else if (v == playBtn)
    {
      playPlaylistItems();
    } else if (v == stopBtn)
    {
      stop();
    }
  }
  private void parsePlaylistFile()
  {
    // TODO Auto-generated method stub
    playlistItems = new Vector();
    // 为了从Web获取M3U文件,可以使用Apache软件基金会的HttpClient库,
    // 它已被android所包括。
    // 首先创建一个HttpClient对象,其代表类似Web浏览器的事物;
    HttpClient httpClient = new DefaultHttpClient();
    // 然后创建一个HttpGet对象,其表示指向一个文件的具体请求。
    HttpGet getRequest = new HttpGet(editTextUrl.getText().toString());
    Log.v("URI", getRequest.getURI().toString());
    // HttpClient将执行HttpGet,并返回一个HttpResponse
    try
    {
      HttpResponse httpResponse = httpClient.execute(getRequest);
      if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
      {
        Log.v("HTTP ERROR", httpResponse.getStatusLine()
            .getReasonPhrase());
      } else
      {
        // 在发出请求之后,可以从HttpRequest中获取一个InputStream,
        // 其包含了所请求文件的内容
        InputStream inputStream = httpResponse.getEntity().getContent();
        // 借助一个BufferedReader可以逐行得遍历该文件
        BufferedReader bufferedReader = new BufferedReader(
            new InputStreamReader(inputStream));
        String line;
        while ((line = bufferedReader.readLine()) != null)
        {
          Log.v("PLAYLISTLINE", "ORIG:" + line);
          if (line.startsWith("#"))
          {
            // 元数据,可以做更多的处理,但现在忽略它
          } else if (line.length() > 0)
          {
            // 如果它的长度大于0,那么就假设它是一个播放列表条目
            String filePath = "";
            if (line.startsWith("http://"))
            {
              // 如果行以“http://”开头那么就把它作为流的完整URL
              filePath = line;
            } else
            {
              // 否则把它作为一个相对的URL,
              // 同时把针对该M3U文件的原始请求的URL附加上去
              filePath = getRequest.getURI().resolve(line)
                  .toString();
            }
            // 将其添加到播放列表条目的容器中去
            PlaylistFile playlistFile = new PlaylistFile(filePath);
            playlistItems.add(playlistFile);
          }
        }
      }
    } catch (ClientProtocolException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    playBtn.setEnabled(true);
  }
  private void playPlaylistItems()
  {
    playBtn.setEnabled(false);
    currentPlaylistItemNumber = 0;
    if (playlistItems.size() > 0)
    {
      String path = ((PlaylistFile) playlistItems
          .get(currentPlaylistItemNumber)).getFilePath();
      // 在提取出流的或者文件的路径之后,就可以在MediaPlayer上的setDataSource方法使用它了
      try
      {
        mediaPlayer.setDataSource(path);
        mediaPlayer.prepareAsync();
      } catch (IllegalArgumentException e)
      {
        e.printStackTrace();
      } catch (IllegalStateException e)
      {
        e.printStackTrace();
      } catch (IOException e)
      {
        e.printStackTrace();
      }
    }
  }
  private void stop()
  {
    mediaPlayer.pause();
    playBtn.setEnabled(true);
    stopBtn.setEnabled(false);
  }
  class PlaylistFile
  {
    String filePath;
    public PlaylistFile(String _filePath)
    {
      filePath = _filePath;
    }
    public void setFilePath(String _filePath)
    {
      filePath = _filePath;
    }
    public String getFilePath()
    {
      return filePath;
    }
  }
}
目录
相关文章
|
4月前
|
网络安全 Python
Python网络编程小示例:生成CIDR表示的IP地址范围
本文介绍了如何使用Python生成CIDR表示的IP地址范围,通过解析CIDR字符串,将其转换为二进制形式,应用子网掩码,最终生成该CIDR块内所有可用的IP地址列表。示例代码利用了Python的`ipaddress`模块,展示了从指定CIDR表达式中提取所有IP地址的过程。
105 6
|
5月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
311 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
5月前
|
网络协议 Shell 网络安全
解决两个 Android 模拟器之间无法网络通信的问题
让同一个 PC 上运行的两个 Android 模拟器之间能相互通信,出(qiong)差(ren)的智慧。
60 3
|
5月前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
67 1
|
7月前
|
安全 网络安全 Android开发
安卓与iOS开发:选择的艺术网络安全与信息安全:漏洞、加密与意识的交织
【8月更文挑战第20天】在数字时代,安卓和iOS两大平台如同两座巍峨的山峰,分别占据着移动互联网的半壁江山。它们各自拥有独特的魅力和优势,吸引着无数开发者投身其中。本文将探讨这两个平台的特点、优势以及它们在移动应用开发中的地位,帮助读者更好地理解这两个平台的差异,并为那些正在面临选择的开发者提供一些启示。
148 56
|
7月前
|
Android开发
Android 利用MediaPlayer实现音乐播放
本文提供了一个简单的Android MediaPlayer音乐播放示例,包括创建PlayerActivity、配置AndroidManifest.xml和activity_player.xml布局,以及实现播放和暂停功能的代码。
70 0
Android 利用MediaPlayer实现音乐播放
|
7月前
|
编解码 网络协议 开发工具
Android平台如何实现多路低延迟RTSP|RTMP播放?
本文档详细介绍了大牛直播SDK在Android平台上实现RTSP与RTMP流媒体播放及录像功能的技术细节。早在2015年,SDK的第一版就已经支持了多实例播放,并且通过简单的实例封装就能轻松实现。文档中提供了代码示例,展示了如何开启播放、停止播放以及开始和停止录像等功能。此外,SDK还提供了丰富的配置选项,例如设置录像目录、文件大小限制、转码选项等。总结部分列出了该SDK的关键特性,包括但不限于高稳定性和低延迟的播放能力、多实例支持、事件回调、硬解码支持、网络状态监控以及复杂的网络环境处理等。这些功能使得SDK能够应对各种应用场景,特别是在对延迟和稳定性有极高要求的情况下表现优异。
141 5
|
7月前
|
编解码 网络协议 vr&ar
Android平台下VR头显如何低延迟播放4K以上超高分辨率RTSP|RTMP流
这段内容讲述了VR头显中实现高分辨率视频播放的技术背景与实现方法,并强调了其重要性。高分辨率对于提升VR体验至关重要,它能提供更清晰的画面、增强沉浸感、补偿透镜放大效应,并维持宽广视场角下的图像质量。文中提到的大牛直播SDK具备极低的延迟(200-400ms),支持多种协议与格式,并具有丰富的功能特性,如多实例播放、事件回调、视频及音频格式支持等。此外,提供了基于Unity的播放器示例代码,展示了如何配置播放参数并开始播放。最后,作者指出此类技术在远程控制、虚拟仿真等应用场景中的重要意义。
|
7月前
|
安全 开发者 数据安全/隐私保护
Xamarin 的安全性考虑与最佳实践:从数据加密到网络防护,全面解析构建安全移动应用的六大核心技术要点与实战代码示例
【8月更文挑战第31天】Xamarin 的安全性考虑与最佳实践对于构建安全可靠的跨平台移动应用至关重要。本文探讨了 Xamarin 开发中的关键安全因素,如数据加密、网络通信安全、权限管理等,并提供了 AES 加密算法的代码示例。
96 0
|
7月前
|
安全 网络安全 Android开发
探索安卓开发之旅:从新手到专家网络安全与信息安全:防范网络威胁,保护数据安全
【8月更文挑战第29天】在这篇技术性文章中,我们将踏上一段激动人心的旅程,探索安卓开发的世界。无论你是刚开始接触编程的新手,还是希望提升技能的资深开发者,这篇文章都将为你提供宝贵的知识和指导。我们将从基础概念入手,逐步深入到安卓开发的高级主题,包括UI设计、数据存储、网络通信等方面。通过阅读本文,你将获得一个全面的安卓开发知识体系,并学会如何将这些知识应用到实际项目中。让我们一起开启这段探索之旅吧!

热门文章

最新文章

  • 1
    Android历史版本与APK文件结构
  • 2
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 3
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 4
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 5
    【03】微信支付商户申请下户到配置完整流程-微信开放平台创建APP应用-填写上传基础资料-生成安卓证书-获取Apk签名-申请+配置完整流程-优雅草卓伊凡
  • 6
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 7
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
  • 8
    escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
  • 9
    Android实战经验之Kotlin中快速实现MVI架构
  • 10
    即时通讯安全篇(一):正确地理解和使用Android端加密算法