我制作了一个 radio 应用程序,效果非常好。我也可以播放 radio 流并获取元数据。流媒体服务来自 shoutcast。
唯一的问题是,我将 URL 作为数据源添加到媒体播放器,然后每 5 秒获取一次标题和艺术家。
有什么办法,我可以只发出一个 HTTP 请求,然后拆分音频和元数据,然后将其发送到媒体播放器?
获取元数据的代码。
private void retreiveMetadata() throws IOException {
int metaDataOffset = 0;
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.addHeader("Icy-MetaData", "1")
.addHeader("Connection", "close")
.addHeader("Accept", "")
.url(streamUrl)
.build();
request.headers("");
Response response = client.newCall(request).execute();
InputStream stream = response.body().byteStream();
//Map<String, List<String>> headers = response..getHeaderFields();
if (!response.headers("icy-metaint").equals("")) {
// Headers are sent via HTTP
String icyMetaInt = response.headers("icy-metaint").toString();
icyMetaInt = icyMetaInt.replace("[", "");
icyMetaInt = icyMetaInt.replace("]", "");
metaDataOffset = Integer.parseInt(icyMetaInt);
} else {
// Headers are sent within a stream
StringBuilder strHeaders = new StringBuilder();
char c;
while ((c = (char)stream.read()) != -1) {
strHeaders.append(c);
if (strHeaders.length() > 5 && (strHeaders.substring((strHeaders.length() - 4), strHeaders.length()).equals("\r\n\r\n"))) {
// end of headers
break;
}
}
// Match headers to get metadata offset within a stream
Pattern p = Pattern.compile("\\r\\n(icy-metaint):\\s*(.*)\\r\\n");
Matcher m = p.matcher(strHeaders.toString());
if (m.find()) {
metaDataOffset = Integer.parseInt(m.group(2));
}
}
// In case no data was sent
if (metaDataOffset == 0) {
isError = true;
return;
}
// Read metadata
int b;
int count = 0;
int metaDataLength = 4080; // 4080 is the max length
boolean inData = false;
StringBuilder metaData = new StringBuilder();
// Stream position should be either at the beginning or right after headers
while ((b = stream.read()) != -1) {
count++;
// Length of the metadata
if (count == metaDataOffset + 1) {
metaDataLength = b * 16;
}
if (count > metaDataOffset + 1 && count < (metaDataOffset + metaDataLength)) {
inData = true;
} else {
inData = false;
}
if (inData) {
if (b != 0) {
metaData.append((char)b);
}
}
if (count > (metaDataOffset + metaDataLength)) {
break;
}
}
// Set the data
metadata = IcyStreamMeta.parseMetadata(metaData.toString());
// Close
stream.close();
}
public static Map<String, String> parseMetadata(String metaString) {
Map<String, String> metadata = new HashMap();
String[] metaParts = metaString.split(";");
Pattern p = Pattern.compile("^([a-zA-Z]+)=\\'([^\\']*)\\'$");
Matcher m;
for (int i = 0; i < metaParts.length; i++) {
m = p.matcher(metaParts[i]);
if (m.find()) {
metadata.put((String)m.group(1), (String)m.group(2));
}
}
return metadata;
}
并将 url 传递给媒体播放器的数据源
String url = "http://68.68.109.106:8356/";
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mp.setDataSource(url);
mp.prepare();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
// TODO Auto-generated catch block
Log.e(TAG, "SecurityException");
Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
Log.e(TAG, "IllegalStateException");
Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e(TAG, "IOException");
Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show();
}
最佳答案
您尝试完成的任务并非易事。 (但这并非不可能。)流的元数据仅在流的开头“下载”,因此之后更改它不会影响从流中读取“缓存”的元信息。要读取新属性,您必须重新启动流,这将获取新的 header 等。(但它可能会导致流中断,因此不推荐这样做。)
在声音技术中,通常使用 watermarking .这是一个以不破坏(质量)的方式使用某种数据丰富声音的过程。 ( Usage on YouTube. ) 虽然很难做到,但有一些方法可以在流中隐藏您的信息。我建议阅读 this实现您想要达到的目标。
由于您的硬件是手机,并非所有 Android 设备都足够强大,因此您应该考虑一些新的 HTTP 请求。就 CPU、内存等而言,音频处理并不是一件便宜的事情。如果您这样做,还有一些其他选项需要考虑。五秒轮询不是获取信息的最佳方式,因为您可能会向用户显示错误信息,而错误信息总比没有更糟糕。我推荐基于mqtt的服务器端推送。 Here是一个很好的用法示例。基于这种方法而不是轮询的解决方案使用更少的流量并且更准确。
关于java - 如何在不发出单独请求的情况下从 shoutcast 流中分离元数据和轨道,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37554620/
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
这是在Ruby中设置默认值的常用方法:classQuietByDefaultdefinitialize(opts={})@verbose=opts[:verbose]endend这是一个容易落入的陷阱:classVerboseNoMatterWhatdefinitialize(opts={})@verbose=opts[:verbose]||trueendend正确的做法是:classVerboseByDefaultdefinitialize(opts={})@verbose=opts.include?(:verbose)?opts[:verbose]:trueendend编写Verb
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这
我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"
我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re
我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121