- 浏览: 51612 次
- 来自: 杭州
文章分类
最新评论
-
fuyun:
lym6520 写道Firefox下根本显示不了。。。
不会, ...
酷觉网络电台ExtJs版 -
lym6520:
Firefox下根本显示不了。。。
酷觉网络电台ExtJs版 -
fuyun:
chris_in 写道12 mms://live.hitfm. ...
国内外所有电台在线播放地址1(全 - 共3595条 - 09-11-30晚更新) -
chris_in:
12 mms://live.hitfm.cn/fm887
的效 ...
国内外所有电台在线播放地址1(全 - 共3595条 - 09-11-30晚更新) -
fuyun:
<div class="quote_title ...
实现龙卷风收音机客户端电台数据解密算法
最新说明:自7月份始,文章公布后,Cradio官方更新了加密算法,故以下实现只对7月份之前的版本有效.关于最新的电台数据,目前只能通过js的方式获取,而相关的除电台名称,地址之外信息还需另外搜集和整理.在此,请诸位自行决定.(再次声明:js获取数据方式因可能对Cradio官方服务器产生不良影响,在此不再公布.)
以下是解密算法的java实现,已通过验证:
ps下,我现在一般听怀旧金曲,都市流行和中国之声,经济之声,还有几个地方电台.关于musicRadio都很少听了.确实,广告太多了.而且还太频繁了,半个小时一次,听着不爽.
电台倒是全部都有了(就是fifm和cradio上面的全部电台数据),但不可能全部都收听过来,如果你需要的话,我可以把你需要的分类进行筛选一下发给你.呵呵~
算法的说明和描述请参看上一篇文章.这里不再重复说明!
以下是解密算法的java实现,已通过验证:
public class Test { public static void main(String[] args) { RadioParser parser = new RadioParser("f:/data.idp", "f:/data.dat"); for(RadioSegement segement : parser) { System.out.println(segement.getRadioInfo()); System.out.println("\n\n"); } } } public class RadioSegement { private int startAddr; private int endAddr; private String info; private RadioInfo radioInfo; public RadioSegement(int startAddr, int endAddr) { this.startAddr = startAddr; this.endAddr = endAddr; } public int getStartAddr() { return startAddr; } public int getEndAddr() { return endAddr; } public int length() { return endAddr - startAddr; } void setInfo(String info) { this.info = info; } public String getInfo() { return info; } public RadioInfo getRadioInfo() { if(radioInfo == null) { radioInfo = new RadioInfo(this); } return radioInfo; } public String toString() { return "startAddr: " + startAddr + ", endAddr: " + endAddr + ", info: " + info; } } public class RadioInfo { private String[] data; private static int index = 0; private final static int INDEX_NAME = index++; private final static int INDEX_TYPE = index++; private final static int INDEX_COUNTRY = index++; private final static int INDEX_PROVINCE = index++; private final static int INDEX_CITY = index++; private final static int INDEX_LANGUAGE = index++; private final static int INDEX_FORMAT = index++; private final static int INDEX_VALIDITY = index++; private final static int INDEX_SPEED = index++; private final static int INDEX_FREQUENCY = index++; static { index++; } private final static int INDEX_HOME_URL = index++; private final static int INDEX_PROGRAMME_URL = index++; private final static int INDEX_BROADCAST_URL = index++; RadioInfo(RadioSegement segement) { data = segement.getInfo().split("\t", -1); if(data.length != 14) { return; } } public String getName() { return data[INDEX_NAME]; } public String getType() { return data[INDEX_TYPE]; } public String getCountry() { return data[INDEX_COUNTRY]; } public String getProvince() { return data[INDEX_PROVINCE]; } public String getCity() { return data[INDEX_CITY]; } public String getLanguage() { return data[INDEX_LANGUAGE]; } public String getFormat() { return data[INDEX_FORMAT]; } public String getValidity() { return data[INDEX_VALIDITY]; } public String getSpeed() { return data[INDEX_SPEED]; } public String getFrequency() { return data[INDEX_FREQUENCY]; } public String getHomeUrl() { return data[INDEX_HOME_URL]; } public String getProgrammeUrl() { return data[INDEX_PROGRAMME_URL]; } public String getBroadcastUrl() { return data[INDEX_BROADCAST_URL]; } public String toString() { return new StringBuilder() .append("\n名称: ").append(getName()) .append("\n类型: ").append(getType()) .append("\n国家: ").append(getCountry()) .append("\n省份: ").append(getProvince()) .append("\n城市: ").append(getCity()) .append("\n语言: ").append(getLanguage()) .append("\n格式: ").append(getFormat()) .append("\n有效性: ").append(getValidity()) .append("\n速率: ").append(getSpeed()) .append("\n频率: ").append(getFrequency()) .append("\n主页: ").append(getHomeUrl()) .append("\n节目表: ").append(getProgrammeUrl()) .append("\n广播地址: ").append(getBroadcastUrl()) .toString(); } } import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.bao.util.io.FileUtil; import com.bao.util.lang.ByteUtil; public class RadioParser implements Iterable<RadioSegement>{ /** * 解码字符 */ private final static char[] xorChar = "jin_cr".toCharArray(); /** * 地址文件 */ private File addrFile; /** * 数据文件 */ private File dataFile; /** * 数据段 */ private List<RadioSegement> segements = null; public RadioParser(String addrFilename, String dataFilename) { this.addrFile = new File(addrFilename); this.dataFile = new File(dataFilename); } @Override public Iterator<RadioSegement> iterator() { if(segements == null) { initAddrList(); initRadioData(); } return segements.iterator(); } /** * 解析地址 */ private void initAddrList() { segements = new LinkedList<RadioSegement>(); BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(addrFile)); int maxLen = (int)dataFile.length(); byte[] bys = new byte[4]; for(int start = 0; bis.read(bys) != -1; ) { int end = ByteUtil.bytes2IntLE(bys); if(end == 0) { continue; } if(end > maxLen) { break; } segements.add(new RadioSegement(start, end)); start = end; } }catch(IOException e) { e.printStackTrace(); }finally{ try { FileUtil.closeIO(bis); } catch (IOException e) { e.printStackTrace(); } } } /** * 解析数据段 */ private void initRadioData() { BufferedInputStream bis = null; try { bis = new BufferedInputStream(new FileInputStream(dataFile)); for(RadioSegement segement : segements) { byte[] bys = new byte[segement.length()]; bis.read(bys); segement.setInfo(parseBytes(bys)); } }catch(IOException e) { e.printStackTrace(); }finally{ try { FileUtil.closeIO(bis); } catch (IOException e) { e.printStackTrace(); } } } /** * 解码 * @param bys * @return * @throws UnsupportedEncodingException */ private static String parseBytes(byte[] bys) throws UnsupportedEncodingException { if(bys == null) { return null; } if(bys.length == 0) { return ""; } int offset = 0; while(offset < bys.length && bys[offset++] != 0x09); for(int i = offset, k = 0; i < bys.length; i++, k++) { bys[i] = (byte)(bys[i] ^ xorChar[k % xorChar.length]); } return new String(bys, offset, bys.length - offset - 2, "gbk"); } } import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * 读取文件工具类 * * @author bao110908 * @since 2008-12-14 */ public class FileUtil { /** * 行分隔符 */ public final static String LINE_SEPARATOR = System.getProperty("line.separator"); /** * 建一个目录 * @param file */ public static boolean createDirectory(File file) { if(file.exists()) { return true; } return file.mkdirs(); } public static boolean createDirectory(String dirname) { return createDirectory(new File(dirname)); } /** * 从文件名中读取文件 * @param filename * @return * @throws IOException */ public static String readFile2String(String filename) throws IOException { return readFile2String(new File(filename)); } /** * 从 File 对象中读取文件 * @param file * @return * @throws IOException */ public static String readFile2String(File file) throws IOException { if((file == null) || !file.exists() || file.isDirectory()) { return null; } return readInputStream2String(new FileInputStream(file)); } /** * 使用系统默认字符集读取二进制流 * @param is * @return * @throws IOException */ public static String readInputStream2String(InputStream is) throws IOException { return readInputStream2String(is, Charset.defaultCharset().name()); } /** * 使用指定编码读取二进制流 * @param is * @param charset * @return * @throws IOException */ public static String readInputStream2String(InputStream is, String charset) throws IOException { BufferedReader br = null; StringBuilder sb = new StringBuilder(); try { br = new BufferedReader(new InputStreamReader(is, charset)); for(String str = null; (str = br.readLine()) != null; ) { sb.append(str).append(LINE_SEPARATOR); } } finally { closeIO(br); } return sb.toString().trim(); } public static List<String> readFile2List(String filename) throws IOException { return readFile2List(new File(filename)); } /** * 将文件读取成为 List,List 中的每一个元素是文件中的一行字符串 * @param file * @return * @throws IOException */ public static List<String> readFile2List(File file) throws IOException { if((file == null) || !file.exists() || file.isDirectory()) { return null; } BufferedReader br = null; List<String> list = new ArrayList<String>(); try { br = new BufferedReader(new FileReader(file)); for(String str = null; (str = br.readLine()) != null; ) { list.add(str); } } finally { closeIO(br); } return list; } public static void writeString2File(String str, String filename) throws IOException { writeString2File(str, new File(filename)); } public static void writeString2File(String str, File file) throws IOException { BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(file)); bw.write(str); } finally { closeIO(bw); } } public static void writeList2File(List<String> list, String filename) throws IOException { writeList2File(list, new File(filename), LINE_SEPARATOR); } public static void writeList2File(List<String> list, File file) throws IOException { writeList2File(list, file, LINE_SEPARATOR); } public static void writeList2File(List<String> list, String filename, String lineSeparator) throws IOException { writeList2File(list, new File(filename), lineSeparator); } /** * 以 List 中的每一个元素作为写入文件中的一行 * @param list * 元素集 * @param file * 文件 * @param lineSeparator * 每个元素写入文件时的分隔符 * @throws IOException */ public static void writeList2File(List<String> list, File file, String lineSeparator) throws IOException { StringBuffer sb = new StringBuffer(); for(int i = 0, k = list.size(); i < k; i++) { if(i > 0) { sb.append(lineSeparator); } sb.append(list.get(i)); } writeString2File(sb.toString(), file); } /** * 关闭 IO 流 * @param io * @throws IOException */ public static void closeIO(Closeable io) throws IOException { if(io != null) { io.close(); } } } /** * 字节工具类 * @author bao110908 * @since 2009-03-07 */ public class ByteUtil { private final static char[] HEX = "0123456789abcdef".toCharArray(); /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用 * 一个空格分隔<br /> * 采用实现较为高效的 bytes2Hex 方法 * @param bys 需要转换成 16 进制的字节数组 * @return * @deprecated */ public static String bytes2Hex1(byte[] bys) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < bys.length; i++) { if(i > 0) { sb.append(" "); } sb.append(HEX[bys[i] >> 4 & 0xf]); sb.append(HEX[bys[i] & 0xf]); } return sb.toString(); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用 * 一个空格分隔 * @param bys 需要转换成 16 进制的字节数组 * @return */ public static String bytes2HexSpace(byte[] bys) { char[] chs = new char[bys.length * 2 + bys.length - 1]; for(int i = 0, offset = 0; i < bys.length; i++) { if(i > 0) { chs[offset++] = ' '; } chs[offset++] = HEX[bys[i] >> 4 & 0xf]; chs[offset++] = HEX[bys[i] & 0xf]; } return new String(chs); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示 * * @param bys 需要转换成 16 进制的字节数组 * @return */ public static String bytes2Hex(byte[] bys) { char[] chs = new char[bys.length * 2]; for(int i = 0, offset = 0; i < bys.length; i++) { chs[offset++] = HEX[bys[i] >> 4 & 0xf]; chs[offset++] = HEX[bys[i] & 0xf]; } return new String(chs); } /** * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,字节间没有分隔 * 符。<br /> * 采用实现较为高效的 bytes2Hex 方法 * @param bys 需要转换成 16 进制的字节数组 * @return * @deprecated */ public static String bytes2Hex2(byte[] bys) { StringBuilder sb = new StringBuilder(); for(int i = 0; i < bys.length; i++) { sb.append(HEX[bys[i] >> 4 & 0xf]); sb.append(HEX[bys[i] & 0xf]); } return sb.toString(); } public static byte[] int2BytesBE(int num) { byte[] bys = new byte[Integer.SIZE / Byte.SIZE]; for(int i = 0, k = bys.length; i < k; i++) { bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff); } return bys; } public static byte[] int2BytesLE(int num) { return int2BytesBE(Integer.reverseBytes(num)); } /** * 采用 Big-Endian 方式将 long 数据转为 byte 数组 * * @param num * @return 转为 Big-Endian 方式的 byte 数组 */ public static byte[] long2BytesBE(long num) { byte[] bys = new byte[Long.SIZE / Byte.SIZE]; for(int i = 0, k = bys.length; i < k; i++) { bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff); } return bys; } /** * 采用 Little-Endian 方式将 long 数据转为 byte 数组 * * @param num * @return 转为 Little-Endian 方式的 byte 数组 */ public static byte[] long2BytesLE(long num) { return long2BytesBE(Long.reverseBytes(num)); } /** * 将 Little-Endian 的字节数组转为 int 类型的数据<br /> * Little-Endian 表示高位字节在低位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 LE 方式表示的 int 数值 */ public static int bytes2IntLE(byte[] bys, int start, int len) { return bytes2Int(bys, start, len, false); } public static int bytes2IntLE(byte[] bys) { return bytes2Int(bys, 0, bys.length, false); } /** * 将 Big-Endian 的字节数组转为 int 类型的数据<br /> * Big-Endian 表示高位字节在高位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 BE 方式表示的 int 数值 */ public static int bytes2IntBE(byte[] bys, int start, int len) { return bytes2Int(bys, start, len, true); } private static int bytes2Int(byte[] bys, int start, int len, boolean isBigEndian) { int n = 0; for(int i = start, k = start + len % (Integer.SIZE / Byte.SIZE + 1); i < k; i++) { n |= (bys[i] & 0xff) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE); } return n; } /** * 将 Little-Endian 的字节数组转为 long 类型的数据<br /> * Little-Endian 表示高位字节在低位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 LE 方式表示的 long 数值 */ public static long bytes2LongLE(byte[] bys, int start, int len) { return bytes2Long(bys, start, len, false); } public static long bytes2LongLE(byte[] bys) { return bytes2Long(bys, 0, bys.length, false); } /** * 将 Big-Endian 的字节数组转为 long 类型的数据<br /> * Big-Endian 表示高位字节在高位索引中 * @param bys 字节数组 * @param start 需要转换的开始索引位数 * @param len 需要转换的字节数量 * @return 指定开始位置和长度以 BE 方式表示的 long 数值 */ public static long bytes2LongBE(byte[] bys, int start, int len) { return bytes2Long(bys, start, len, true); } public static long bytes2LongBE(byte[] bys) { return bytes2Long(bys, 0, bys.length, true); } private static long bytes2Long(byte[] bys, int start, int len, boolean isBigEndian) { long n = 0L; for(int i = start, k = start + len % (Long.SIZE / Byte.SIZE + 1); i < k; i++) { n |= (bys[i] & 0xffL) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE); } return n; } }
评论
6 楼
fuyun
2009-09-03
fjlyxx 写道
很好的软件 上班听听 就是现在的广播广告太多了.. 评书类节目太少了 各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢
ps下,我现在一般听怀旧金曲,都市流行和中国之声,经济之声,还有几个地方电台.关于musicRadio都很少听了.确实,广告太多了.而且还太频繁了,半个小时一次,听着不爽.
5 楼
fuyun
2009-09-03
fjlyxx 写道
很好的软件 上班听听 就是现在的广播广告太多了.. 评书类节目太少了 各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢
电台倒是全部都有了(就是fifm和cradio上面的全部电台数据),但不可能全部都收听过来,如果你需要的话,我可以把你需要的分类进行筛选一下发给你.呵呵~
4 楼
fjlyxx
2009-09-01
很好的软件 上班听听 就是现在的广播广告太多了.. 评书类节目太少了 各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢
3 楼
raomengwen
2009-09-01
算法很好,但是看不懂,说明太少了
2 楼
fuyun
2009-07-02
zhiblin 写道
楼主能不能多一点文字说明一下,毕竟是算法
算法的说明和描述请参看上一篇文章.这里不再重复说明!
1 楼
zhiblin
2009-07-02
楼主能不能多一点文字说明一下,毕竟是算法
相关推荐
【龙卷风收音机】是一款流行的网络收音机应用,以其丰富的电台资源和便捷的使用体验深受用户喜爱。在这款软件中,`.data` 文件通常用于存储应用程序的配置信息、资源或者加密的数据,以便保护应用程序的核心内容不被...
龙卷风收音机通过优化的数据压缩技术和高效的流媒体处理,确保了即使在网络环境不理想的情况下,也能流畅播放电台节目,减少了缓冲和卡顿的情况,让用户沉浸在连续无断的音频享受中。 其次,电台资源的丰富性是龙卷...
总结来说,龙卷风网络收音机凭借其最新的版本、丰富的皮肤、全面的功能、实时更新的数据以及优秀的休闲娱乐属性,成为了一款值得信赖的网络收音机应用。无论是对于追求新鲜资讯的新闻迷,还是热爱音乐的发烧友,或者...
龙卷风网络收音机7.6版是一款专为此趋势打造的软件,它将全球各地的广播电台汇聚一堂,让用户在指尖就能畅享丰富的音频内容。 一、软件概述 龙卷风网络收音机7.6版,正如其名,以其强大的功能和流畅的体验,像一股...
华为ep680上很好用的收音机,ep680由于系统版本低,可以用的软件很少,这个软件非常流畅,收到台也很多,速度很快
总结,龙卷风网络收音机v3.0.1.3以其丰富的电台资源、便捷的操作方式和个性化的皮肤设计,为用户提供了一站式的网络广播体验。无论是安装包还是绿色包,都能满足不同用户的需求,而多样化的皮肤则让软件更具吸引力。...
"龙卷风网络收音机"是一款专为用户设计的在线广播软件,它集成了丰富的网络电台资源,让用户能够随时随地享受各种音乐、新闻、谈话节目等。这款软件以其便捷的操作和高质量的音频流服务,深受广大听众喜爱。下面将...
在线收听广播
“龙卷风收音机”拥有全球范围内的海量电台频道,涵盖各种类型的电台节目,包括新闻、音乐、体育、谈话节目等,满足不同用户的个性化需求。用户可以根据自己的喜好选择国内外的电台,享受无国界的音频盛宴。 二、...
"龙卷风网络收音机3.0"是一款广受欢迎的网络广播软件,它以其出色的音质、丰富的电台资源和用户友好的界面赢得了用户的青睐。作为一款强大的在线音频播放工具,它不仅支持实时收听全球各地的广播电台,还具备录音、...
"龙卷风网络收音机"是一款专为网络广播爱好者设计的应用程序,它允许用户在全球范围内收听各种电台节目,只需接入互联网即可。这款软件打破了传统收音机的地域限制,让用户可以随时随地享受来自世界各地的声音。 在...
龙卷风网络收音机 在线听广播 全国的电台任你听 收音机可以扔掉了 这个是最新版
总结来说,【龙卷风收音机】凭借其丰富的电台资源、简便的操作方式、稳定的技术支持以及免费的使用体验,成为了许多用户心目中的首选网络收音机工具。无论是想了解国内外新闻,还是想在闲暇时聆听美妙的音乐,它都能...