一、微信小程序开发之前必须要完成和注册认证,申请小程序费用为300元,如果有已经认证过的公众号就可以免费申请。
二、小程序开发:分为两种情况
第一种自己开发,费用的话就是自己技术的工资、绩效和服务器。
第二种找第三方开发公司,这里分为两种情况:
第1种是卖模板为主的网络公司。
优点是:价格低,几千块钱到万元之间就能搞定,方便,能够快速上线;
缺点是:修改功能麻烦,这里需要避免低价陷阱,不要到最后才发现模板性的修改功能所花的钱比买模板还贵。
第2种是定制开发为主的网络公司。
优点是:专为你的企业或者店面定制的,功能你来定,要求你来定,后期修改BUG方便,改东西也很方便。
缺点是:相对价格比较高!定制版的基本费用在上万元到十几万不等!不过贵也有贵的道理吧,毕竟功能做的更全面一点。
一、微信小程序开发之前必须要完成和注册认证,申请小程序费用为300元,如果有已经认证过的公众号就可以免费申请。
二、小程序开发:分为两种情况
第一种自己开发,费用的话就是自己技术的工资、绩效和服务器。
第二种找第三方开发公司,这里分为两种情况:
第1种是卖模板为主的网络公司。
优点是:价格低,几千块钱到万元之间就能搞定,方便,能够快速上线;
缺点是:修改功能麻烦,这里需要避免低价陷阱,不要到最后才发现模板性的修改功能所花的钱比买模板还贵。
第2种是定制开发为主的网络公司。
优点是:专为你的企业或者店面定制的,功能你来定,要求你来定,后期修改BUG方便,改东西也很方便。
缺点是:相对价格比较高!定制版的基本费用在上万元到十几万不等!不过贵也有贵的道理吧,毕竟功能做的更全面一点。
普通类微信小程序的定制费用在2-5万元左右,高档类小程序的费用在8万元左右,具体需要根据客户的需求才能进行评估报价。小程序开发联系电话:******
小程序定制开发的价格在几千到几十万不等,不同的价格对应不同类型的公司需求,而且小程序后期可以更改,非常适合初创企业和发展中的企业。微信上附近的小程序功能,可以帮助商家被五公里范围内的微信用户搜索到,解决当下商家广告无处可打的尴尬,有助于企业产品和品牌的传播。小程序开发联系电话:******,联系QQ:******
那么长沙小程序制作后引流应该怎样做呢?
1、关键词推广
每一个小程序都有10个搜索关键词可进行设置,用于在线上微信搜索功能被用户搜索到。而运营者可以有效地利用这10个关键词,进行对自己的小程序进行推广宣传。让更多的用户通过在微信搜索功能触达自己的小程序。
此外,小程序的名称也非常重要,通过小程序名称进行精准搜索,是最直接获取触达小程序的途径。所以,起一个好的小程序的名称就很关键,在小程序开发之初,就得要好好琢磨用户的喜好,当下的热点,结合小程序的产品销售特点来取名。
2、二维码扫码
二维码扫码是线下推广引流最普遍也是最通用的一种方法。企业商户利用二维码做成各种宣传海报和宣传册。甚至是举办地推活动,长沙亿仁网络让更多的用户进行扫码触达小程序。
对于初生的小程序,二维码分享推广,是获取第一批种子用户非常有效的手段,特别适合实体店。
3、好友分享
微信小程序支持直接分享给好友或者分享到微信群里。当开发者做好了自己的小程序以后,可以直接分享给好友或者分享到微信群中,也可以让好友帮助转发。
由于小程序不像公众号还需要关注,小程序打开即可使用。所以只要内容较好,具有一定市场需求和服务质量的小程序。一般来说都会得到众多用户的认可,从而获得裂变式分享传播。
4、与公众号关联
目前,小程序已经可以和公众号进行相互的跳转,e68a847a686964616f31333431373332企业可以通过自己现有的微信公众号直接推广小程序。无论是关联展示,还是自定义菜单设置入口,以及在图文推送的文章嵌入小程序。都能通过公众号前期打下的粉丝基础,进行小程序传播引流。
相对于漫无目的去开发客户,将公众号现有的粉丝及目标客户,通过公众号关联小程序进行推广,让客户直接打开使用,是非常有效的推广引流方式。
线下音乐培训机构开发小程序或APP的方法有以下几种,你可以根据自身实际情况选择一种:
自行开发:
自行开发,就是自己组建开发团队来开发小程序。这种方式的好处是:从开发到后期的维护、升级、改版等,沟通起来都比较方便。但是组建团队,不仅要付出招聘成本,而且还需要支付技术人员的工资。由于需要一定的财力支持,因此往往只有不差钱的大型企业才会选择这种方式。
第三方拖拽平台生成:
第三方拖拽平台生成,就是在第三方平台上,利用第三方平台提供的资源,“一键生成”小程序。选择这种平台来开发小程序的好处是:费用低、方便、快捷。但是,这类平台的小程序往往都是基于模板开发的,因此往往无法很好的凸显出企业的特色、产品的特点,无法给客户留下较深刻的印象。此外,这类平台大多数是按月或按年付费,无形之中增加了企业和商家的开发成本。
找专业的小程序开发服务商
找专业的小程序开发服务商,是较为常用的开发方式。这种方式的好处是:首先是可以省去组建团队的麻烦、省下人力成本;其次,是每一项功能、每一个页面都可以根据自己企业的特色、产品的特色进行定制开发。这样有利于企业更好的做好营销推广工作。
编辑导语:如今,音乐产业数字化发展已成为常态,而数字音乐作为内容生态内的关键一环,通过自身或与其它文娱产品的联动,能带来多大的商业价值呢?一起来看一下吧。
音乐是人们生活中不可缺少的一剂调味品,拥有庞大的受众群体,为大家带来精神慰藉,同时,它也展现出独特的商业价值。
随着时代进步和互联网技术的普及,音乐产业数字化发展已成为常态,而数字音乐作为内容生态内的关键一环,通过自身或与其它文娱产品的联动,为商业模式带来更多可能性。

数字音乐,以数字格式存储,以互联网为平台进行传输的音乐,具有易制作、易传播、易存储的特点。追溯其历史,从1948年的黑胶时代到1963年磁带时代、再到80年代CD,音乐传播及存储形态越来越轻便且易于携带,最终伴随着互联网的发展成为无实物的数字形态,传播和存储全面进入数字化时代,极大程度上提高了音乐的传播效应与普及率。
2003年,苹果推出第三代ipod,并借助ipod播放器和ITUNES音乐商店的结合,正式开启了数字音乐在线试听和购买模式,数字音乐销售正式面市。
虽然数字音乐进入中国的时间可追溯到上个世纪90年代,但当时的音乐环境很不好,盗版横行,用户不愿意为正版付费,原作者也没有维权意识。数字音乐产业真正规模化的发展则是近些年,以政策、环境和资本等因素作为驱动力。
自2010年“剑网行动”起,国家加大打击网络盗版,开展知识产权保护。2015年,《关于责令网络音乐服务商停止未经授权传播音乐的通知》等多项推动数字音乐正版化发展的政策发布,政府展开了针对网络音乐传播最严厉的一次打击盗版和侵权行动,开启数字音乐正版化元年。在政策的大力严控下,数字音乐盗版泛滥的现象得到有效改善,为此后健康、规模化的商业发展打下了基础。
在数字音乐行业正版化之后,其商业化之路也开始走上正轨。头部互联网企业纷纷布局数字音乐产业,激烈竞争过后市场格局逐渐稳定,各平台专注于成熟商业模式的建立和探索。现阶段在国家各类保护政策影响下,用户知识版权意识越来越强烈,也愿意为正版数字音乐付费,庞大的数字音乐付费市场逐渐形成,成为我国音乐产业发展的重要引擎。
02 数字时代音乐的商业应用随着数字时代到来,音乐逐渐嵌入到多元化的场景中,并扮演着重要的角色,这也为中国数字音乐市场带来了巨大的发展空间。
数字音乐的商业化应用:
1)在线音乐
尤其是移动音乐应用,遍布生活的每时每刻满足用户随时享受音乐的需求。
一组用户音乐需求数据显示,在家休息时,音乐占比67.2%,乘坐交通工具时,音乐占55.9%,旅行途中54.7%,锻炼健身时40.4%,排队或等人时38.5%,户外游玩时31.3%,图书馆、咖啡厅时24.7%。
而QQ音乐、网易云音乐等数字音乐平台,采取单曲、数字专辑或会员等形式对用户进行付费转化,这也是音乐下游分发中金额和数额体量最大的一类。
2)电信运营商
如中国移动、中国联通等,通过打包购买的形式,满足用户彩铃、振铃和整曲下载等多样化需求,但近年来该体量明显下降。
3)影视综/游戏OST及配乐等
音乐作为其中重要元素,对内容呈现有着重要影响,此类音乐也多采取定制或单首购买的形式。
4)背景音乐
主要包含短视频/直播、广告营销活动以及商超、咖啡厅等公共场所播放的背景音乐等。此类场景下,音乐可潜移默化的对用户消费产生影响。
5)音乐营销,放大品牌合作、IP传播
通过音乐在情感层面与用户建立共鸣和价值连接,让品牌的口碑和信誉度得到有效提升,品牌影响力沿着音乐能量扩散,不断强化;对于IP孵化,将歌曲和IP结合进行内容深化,可精准切入粉丝经济,实现以点带面的全面扩散,在音乐分发中具有较高价值。
6)音乐综艺节目伴奏
购买歌曲伴奏以便在音乐综艺节目中进行改编或重新演绎。此情况下通常仅需要词曲版权。
7)线下音乐活动
如音乐节/Live House,对歌曲进行现场演绎。
此外,企业也积极探索关于音乐的更多新玩法,催生出app软件、短视频、K歌、在线服务业、智能硬件、有声读物、直播等需要结合音乐的新兴行业,音乐内容作为其中的一环,发挥着重要作用,商业价值愈发明显。
03 数字时代,音乐版权成为最重要的竞争力如果说,正版保护是数字音乐发展的第一步,那音乐版权则让更多商业模式变为可能。
不管是线上的传统广告收益、数字单曲/专辑售卖、音乐周边硬件产品的产销、O2O模式带动的粉丝经济还是演唱会、音乐会、音乐综艺等,在音乐商业变现的道路上,音乐版权已成为最重要的竞争力。这对于数字音乐产业链上的所有创作方、录制方、版权方、分发方都同样重要。
对于创作者(独立音乐人、词曲作者、音乐工作室等),尤其是普通音乐人,没有大牌足够的粉丝积累和商业活动,所以需要更多的依靠版权收入为后续创造缓解生活压力。
对于音乐版权所有方,不管是唱片公司、版权代理公司、国家音乐版权管理监督机构还是数字音乐平台,都积极的通过创作、购买或管理的方式获取音乐版权内容并形成流程化的再分发体系,以此挖掘作品商业价值达到变现目的。
虽然拥有音乐版权数量的多寡是各音乐平台和版权公司开展商业化尝试,争夺市场份额的重要资本。但必须要说的是,只有良性竞争,才能带来源源不断的创新,更好地服务用户。
数字化时代,互联网的发展改变了音乐产品的生产、分销流通乃至消费形式,使得音乐产品摆脱了以往胶片、磁带或是光碟作为载体,并通过有形物理渠道进行分销流通的传统商业模式,取而代之的是数字化、无形化的纯数字音乐通过互联网向消费者传递。音乐产业也由此经历了从传统产业格局向数字化的转型。

当前,数字音乐已成为全球音乐产业最重要的资金来源和毋庸置疑的未来方向,中国数字音乐的重要程度也不言而喻。在政策与企业平台的双重推动下,中国音乐产业正版化、付费潮、数据技术应用以及产业链服务等正逐渐完善,音乐版权价值的持续增长未来可期。
本文@HIFIVE嗨翻屋 原创发布于人人都是产品经理。未经许可,禁止转载。
题图来自 Unsplash ,基于 CC0 协议
买数字藏品吗?1000元制作一张、不限量复制的那种。
2022年以来,数字藏品交易热度不减,从最初绘画、音乐作品到一双鞋、一张门票,从流行潮牌到非遗文化,从互联网到餐饮界……数字藏品覆盖范围不断延伸,“万物皆可NFT”逐渐演变成“万物皆可数字藏品”。
数字藏品的“风”越刮越大,数字藏品交易平台也如星星之火,呈现出燎原之势。在各类公开平台上,一面是新上线的数字藏品交易平台,通过“空投”奖励等形式卖力拉新获客。而另一面,有人做起了数字藏品交易平台App研发的生意,最低3万元可买一个“壳”,最快一周即可上线提供交易。
不仅如此,数字藏品发行也能一并“打包”计价,2D作品每份1000元,3D作品每份2000元,不限量发行,甚至还能帮忙对接支付机构和上链……一条围绕数字藏品平台研发、运营的产业链条,正在悄然生长。
3万元即可搭平台
数字藏品爆火,数字藏品交易平台涌现。紧随其后的,是“NFT数字资产一站式解决方案服务商”这类新群体。“你是想要搭建平台还是发行数字藏品,我们公司都可以做。”7月5日,有业务员向北京商报记者介绍称。
制作一个数字藏品交易平台App需要花费多少钱?前述业务员给出的最低报价是3万元。按照现有的模板,公司根据顾客实际需求修改页面和名称,最快1周内即可上线并提供交易。
从前述业务员提供的案例App来看,该案例App中,在售藏品、寄售市场、公告区、客服等板块一应俱全。在登录页面,还设有《用户协议》《隐私政策》等内容。“就是套模板,所以不会花费太长时间。还有一款带3D动态效果的,‘部署’一套是4万元。另外公司赠送一年系统维护权益。”前述业务人员介绍称。
相较于买“壳”套模板的操作,另一位业务员介绍指出,还可以采用“独立部署”的模式,即支持在原有基础上二次开发和定制功能开发,签约付费后预计2周上线,费用最低6.8万元起步。后续由商家自行进行系统维护。“这类App能够支持更大规模的用户同时在线,也不用担心数字藏品发行时出现网络卡顿、拥挤。”
“实际上的区别,在于App开发的源代码。‘套壳’模式下,源代码由开发者掌握,通过外网映射形成开放域名,购买者无法对App进行后续的修改和维护,开发者可以无限制地将App系统进行售卖;‘独立部署’在源代码的基础上进行了需求加工,购买者可以获得该部分权益,但原始系统仍在开发者手中。”一名计算机领域从业人员解释道。
这两种方式下产生的App,相较于从头研发能节省更多的时间。但这类近乎批量的快销式生产方式下,也隐隐透出了数字藏品领域的“乱”。在浙江大学国际联合商学院数字经济与金融创新研究中心联席主任、研究员盘和林看来,这一情况说明数字藏品沦为赚钱、炒作工具的现象越来越严重。
欧科云链研究院高级研究员蒋照生更是直言,这种现象很好地解释了数字藏品乱象逐渐出现、平台质量良莠不齐的根本原因。当前数字藏品领域规范不清晰,可能存在一部分参与者从一开始就抱着炒作圈钱的目的进入市场。
制作、发行均可外包
在沟通过程中,业务员向北京商报记者展示了多款客户案例。10余家数藏平台名称、风格以及数字藏品类型方面各有不同,但细细看去,在业务分区和版面格式上,并不难发现相似之处。
而服务商的业务,并不仅仅局限于开发App。号称不可篡改、独一无二的各类数字藏品,也可以由服务商代理发行。
“图片类数字藏品制作,2D类型1000元/张,3D类型2000元/张,均支持不限次复制。还可以对接上链和支付平台。”对于公司具体可以开展的业务部分,业务员给出了“一条龙”服务介绍,后续还可以提供营销获客的专属方案等。
其中,上链是数字藏品的价值所在。在数字藏品交易中,平台方将发行的数字资产登记上链,形成独特的链上地址。用户在平台购买对应的数字藏品后,这一链上地址就计入了用户的数字钱包。
从研发到发行再到营销,一切都可以“外包”,数字藏品运营似乎变得简单化、流程化。“数万元买一个‘壳’,再加上对接上链,数字藏品像极了当年买系统、造资产标的的P2P。”7月6日,区块链行业人士李言(化名)打趣道。
对于数字资产上链的情况,李言介绍称,当前国内数字藏品采用的是“联盟链”,大部分数字藏品交易平台本身没有“链条”,只能向开放链条的平台购买相关服务。数字藏品在链上的每一次流转,都会产生新的计费。
“一张图片制作好之后,在链上登记1000次,就会形成1000个地址,也就是交易平台所说的发行了1000份。”李言表示,数字藏品平台运营还涉及到IP版权方、藏品制作方和流量端口等多个环节,平台往往会寻求大公司合作。
蒋照生认为,通过这类“外包”模式进入数字藏品市场的参与主体,并没有真正去理解和研究数字藏品的发展规律和内在逻辑,而是试图通过营销炒作的方式来吸引关注,最终达到自己获利的目的。
终极归宿
当前市场上到底有多少家数字藏品交易平台,谁也没有办法给出准确的数据。300、500、800、1000……在所有关于数字藏品交易平台的讨论中,“越来越多”成为了共识。
无数不知名的数字藏品交易平台,化身一张二维码,一个H5(即“HTML5”),一个网页链接。它们藏在各类公开社交平台或数字藏品相关的社群中,在不断推广下等待更多新用户;也随时准备变身“数字藏品刺客”,在发行方圈完钱后变成一张不再具备交易价值的照片。
过去的几个月间,数字藏品风光无两。大部分数字藏品平台自带二级市场,因数字藏品暴涨而出圈的平台不在少数。但其背后存在的金融化风险也让市场质疑声不断,期间也夹杂着跑路、割韭菜等传言。
“国内数字藏品平台的日成交额在2022年1月达百万元水平,到4月该数字就突破1000万元,较年初增长了10倍有余,”蒋照生透露,从最近几周市场表现来看,数字藏品市场热度正在衰退,一些存在明显问题的数字藏品平台正加速消亡,即便是较为主流的数藏平台也开始采取相对稳妥和保守的策略。
蒋照生认为,代为开发App、发行数字藏品的服务商也是数字藏品爆火后的产物,数字藏品市场正在经历一轮低潮期的考验,这种考验也是市场快速发展之后的自我消化与沉淀。在当前的数藏市场环境下,通过全线外包、代理等模式入场,试图投机取巧、占地圈钱的平台终将被淘汰。
李言团队在2022年上半年也曾计划上线数字藏品交易平台,在李言看来,数字藏品平台的运营并不仅仅在于上线App、发行数字藏品。而在综合考虑到国内监管、市场流通性等多方因素后,李言团队最终放弃了这一创业思路。
“风刮过后只留下一地鸡毛,这并不是我的初衷,”李言坦然道,“从现在数字藏品行业炒作的情况来看,并没有什么实际价值。”
大成律师事务所律师肖飒分析指出,长期来看,数字藏品不应该也不能仅仅局限在数字文化领域,而是需要跳脱出“藏品”的概念范畴,在更多元和更广泛的场景中去应用实践。比如资产数字化。将数字藏品置于整个数字经济的宏观发展框架之下,去思考数字藏品对于产业数字化和数字产业化的积极作用,在合规守正的基本前提下不断尝试新的可能性。
肖飒认为,“跑路”等负面消息会不断刺激人们的神经,如果数字藏品行业持续被“跑路”这样的字眼所包裹,那么整个行业都会陷入到“赚快钱”的节奏之中。数字藏品在我国目前主要是作为文化数字化、发展文化产业、促进文化创新的工具和成果,而绝非是一个金融产品。让文化的归文化,金融的回金融,也许才是数字藏品市场的终极归宿。
北京商报金融调查小组
前言MediaPlayer类可以用来播放音视频文件,或者是音频流。开发者可以用它来播放本地音频,或者是网络在线音频。
MediaPlayer属于android.media包。
MediaPlayer流程按照以下顺序介绍如何用MediaPlayer去构建一个基础的本地音乐播放器。
获取本地音乐数据;构建PlayService,来执行音乐播放任务;构建一个UI与PlayService的中间层——PlayManager,用来处理媒体文件的Playback生命周期;在PlayManager中,加入处理意外情况的方式,所谓意外情况,例如耳机拔出、接到电话、其他播放器播放音乐等;实现远程控制与PlayService保活,例如Notification与锁屏控制;以上是已经实现的部分,以后再逐渐完善的有:
桌面Widget播放控件以及控制;自定义播放列表支持;耳机与蓝牙的播放控制;视频播放支持;远端媒体播放支持;播放的可视化效果;歌词支持;MediaCodec支持;获取本地音乐数据最快的获取本地音乐信息的方式,就是通过ContentProvider获取,我们先构建一个model类Song.java去表示音乐文件:
public class Song { private String title, titleKey, artist, artistKey,album, albumKey, displayName, mimeType, path; private int id, albumId, artistId, duration, size, year, track; private boolean isRingtone, isPodcast, isAlarm, isMusic, isNotification; //private File mCoverFile; private Album albumObj; public Song (Bundle bundle) {id = bundle.getInt(MediaStore.Audio.Media._ID);title = bundle.getString(MediaStore.Audio.Media.TITLE);titleKey = bundle.getString(MediaStore.Audio.Media.TITLE_KEY);artist = bundle.getString(MediaStore.Audio.Media.ARTIST);artistKey = bundle.getString(MediaStore.Audio.Media.ARTIST_KEY);//mComposer = bundle.getString(MediaStore.Audio.Media.COMPOSER);album = bundle.getString(MediaStore.Audio.Media.ALBUM);albumKey = bundle.getString(MediaStore.Audio.Media.ALBUM_KEY);displayName = bundle.getString(MediaStore.Audio.Media.DISPLAY_NAME);year = bundle.getInt(MediaStore.Audio.Media.YEAR);mimeType = bundle.getString(MediaStore.Audio.Media.MIME_TYPE);path = bundle.getString(MediaStore.Audio.Media.DATA);artistId = bundle.getInt(MediaStore.Audio.Media.ARTIST_ID);albumId = bundle.getInt(MediaStore.Audio.Media.ALBUM_ID);track = bundle.getInt(MediaStore.Audio.Media.TRACK);duration = bundle.getInt(MediaStore.Audio.Media.DURATION);size = bundle.getInt(MediaStore.Audio.Media.SIZE);isRingtone = bundle.getInt(MediaStore.Audio.Media.IS_RINGTONE) == 1;isPodcast = bundle.getInt(MediaStore.Audio.Media.IS_PODCAST) == 1;isAlarm = bundle.getInt(MediaStore.Audio.Media.IS_ALARM) == 1;isMusic = bundle.getInt(MediaStore.Audio.Media.IS_MUSIC) == 1;isNotification = bundle.getInt(MediaStore.Audio.Media.IS_NOTIFICATION) == 1; }
然后从ContentProvider中获得手机上的音乐文件:
public static List<Song> getAudioList(Context context) { ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,AUDIO_KEYS,MediaStore.Audio.Media.IS_MUSIC + "=" + 1,null,null); return getAudioList(cursor);}private static List<Song> getAudioList (Cursor cursor) { List<Song> audioList = null; if (cursor.getCount() > 0) {audioList = new ArrayList<Song>();for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {Bundle bundle = new Bundle ();for (int i = 0; i < AUDIO_KEYS.length; i++) {final String key = AUDIO_KEYS[i];final int columnIndex = cursor.getColumnIndex(key);final int type = cursor.getType(columnIndex);switch (type) {case Cursor.FIELD_TYPE_BLOB:break;case Cursor.FIELD_TYPE_FLOAT:float floatValue = cursor.getFloat(columnIndex);bundle.putFloat(key, floatValue);break;case Cursor.FIELD_TYPE_INTEGER:int intValue = cursor.getInt(columnIndex);bundle.putInt(key, intValue);break;case Cursor.FIELD_TYPE_NULL:break;case Cursor.FIELD_TYPE_STRING:String strValue = cursor.getString(columnIndex);bundle.putString(key, strValue);break;}}Song audio = new Song(bundle);audioList.add(audio);} } cursor.close(); return audioList;}
这段代码具体可参考MediaUtils.java。
经过以上代码,便可以得到手机内的所有音乐文件。
构建PlayService,来执行音乐播放任务我们都知道,长时间的后台任务,需要放在Service中进行,我们称这个用来播放音乐的Service为PlayService。
注意在PlayService中的onStartCommand方法的返回值为Service.START_STICKY。
详见Service中关于START_STICKY的解释,其中有这么一段:
START_STICKY
Added in API level 5
int START_STICKY
……..
This mode makes sense for things that will be explicitly started and stopped to run for arbitrary periods of time, such as a service performing background music playback.
可见返回START_STICKY适用于执行音乐播放的Service。
在这个Service类中,我们持有MediaPlayer实例,并实现OnPreparedListener,OnCompletionListener,OnErrorListener等。
我们如此实例化MediaPlayer
private void ensurePlayer () { if (mPlayer == null) {mPlayer = new MediaPlayer(); } setPlayerState(STATE_IDLE); mPlayer.setOnInfoListener(this); mPlayer.setOnPreparedListener(this); mPlayer.setOnCompletionListener(this); mPlayer.setOnErrorListener(this); mPlayer.setOnSeekCompleteListener(this);}
当需要进行音乐播放的时候,再执行此方法
public void startPlayer (String path) { //releasePlayer(); ensurePlayer(); try {mPlayer.setDataSource(path);setPlayerState(STATE_INITIALIZED);mPlayer.prepareAsync();setPlayerState(STATE_PREPARING); } catch (IOException e) {e.printStackTrace();releasePlayer(); }}
在PlayService类中,我们声明一系列播放周期的状态。
public static final int STATE_IDLE = 0, STATE_INITIALIZED = 1, STATE_PREPARING = 2, STATE_PREPARED = 3, STATE_STARTED = 4, STATE_PAUSED = 5, STATE_STOPPED = 6, STATE_COMPLETED = 7, STATE_RELEASED = 8, STATE_ERROR = -1;@IntDef({STATE_IDLE, STATE_INITIALIZED, STATE_PREPARING, STATE_PREPARED, STATE_STARTED, STATE_PAUSED, STATE_STOPPED, STATE_COMPLETED, STATE_RELEASED, STATE_ERROR})@Retention(RetentionPolicy.SOURCE)public @interface State {}private @State int mState = STATE_IDLE;
具体的声明周期图,可以参考谷歌文档中关于MeidaPlayer部分的说明。主要参见下图:
在ensurePlayer这个方法中,状态变更为STATE_IDLE;在MediaPlayer中setDataSource后,状态变更为STATE_INITIALIZED;MediaPlayer执行prepareAsync后,状态变更为STATE_PREPAREING。其余的关键的涉及到播放周期变化的方法如下:
@Overridepublic void onPrepared(MediaPlayer mp) { setPlayerState(STATE_PREPARED); doStartPlayer();}//state -> STATE_PREPAREDprivate void doStartPlayer () { mPlayer.start(); setPlayerState(STATE_STARTED);}public void resumePlayer () { if (isPaused()) {doStartPlayer(); }}//state -> STATE_STARTEDpublic void pausePlayer () { if (isStarted()) {mPlayer.pause();setPlayerState(STATE_PAUSED); }}//state -> STATE_PAUSED@Overridepublic void onCompletion(MediaPlayer mp) { setPlayerState(STATE_COMPLETED);}//state -> STATE_COMPLETEDpublic void releasePlayer () { if (mPlayer != null) {mPlayer.release();mPlayer = null;setPlayerState(STATE_RELEASED); }}//state -> STATE_RELEASED@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) { setPlayerState(STATE_ERROR); return false;}//state -> STATE_ERROR
Service中的其他关键部分
public class PlayBinder extends Binder { public PlayService getService () {return PlayService.this; }}
用来通过bindService的时候返回PlayService的实例。
UI与PlayService的中间层——PlayManager我们将其他的播放逻辑放在这个中间层中,例如下一曲,上一曲,播放规则(单曲循环,列表循环,随机播放等)锁屏显示与Notification显示,还有意外情况的处理,例如失去AudioFocus、耳机插拔、收到电话等。
单例化PlayManager
private static PlayManager sManager = null;public synchronized static PlayManager getInstance (Context context) {if (sManager == null) {sManager = new PlayManager(context.getApplicationContext());}return sManager;}
首先我们要在这个中间层PlayManager里获得PlayService的实例:
private void bindPlayService () {mContext.bindService(new Intent(mContext, PlayService.class), mConnection, Context.BIND_AUTO_CREATE);}private void startPlayService () { mContext.startService(new Intent(mContext, PlayService.class));}private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) {mService = ((PlayService.PlayBinder)service).getService();mService.setPlayStateChangeListener(PlayManager.this);Log.v(TAG, "onServiceConnected");startRemoteControl();if (!isPlaying()) {dispatch(mSong);} } @Override public void onServiceDisconnected(ComponentName name) {Log.v(TAG, "onServiceDisconnected " + name);mService.setPlayStateChangeListener(null);mService = null;startPlayService();bindPlayService(); }};
通常与Service交互,有两种方式,startService和bindService,但是这里要startService与bindService同时进行。这两种方式并不矛盾,详细可以参见绑定服务中的相关描述。
绑定到已启动服务
正如服务文档中所述,您可以创建同时具有已启动和绑定两种状态的服务。 也就是说,可通过调用 startService()启动该服务,让服务无限期运行;此外,还可通过调用 bindService() 使客户端绑定到服务。
如果您确实允许服务同时具有已启动和绑定状态,则服务启动后,系统“不会”在所有客户端都取消绑定时销毁服务。 为此,您必须通过调用stopSelf() 或 stopService() 显式停止服务。
尽管您通常应该实现 onBind() 或onStartCommand(),但有时需要同时实现这两者。例如,音乐播放器可能发现让其服务无限期运行并同时提供绑定很有用处。 这样一来,Activity 便可启动服务进行音乐播放,即使用户离开应用,音乐播放也不会停止。 然后,当用户返回应用时,Activity 可绑定到服务,重新获得回放控制权。
请务必阅读管理绑定服务的生命周期部分,详细了解有关为已启动服务添加绑定时该服务的生命周期信息。
其中特别提到了“音乐播放器可能发现让其服务无限期运行并同时提供绑定很有用处”。
获取到了PlayService的实例后,便可以正式开始音乐的播放了。音乐播放的方法在PlayManager中的dispatch中。
/*** dispatch a song.If the song is paused, then resume.If the song is not started, then start it.If the song is playing, then pause it.* {@link PlayService#STATE_COMPLETED}* @param song the song you want to dispatch, if null, dispatch a song from {@link Rule}.* @see Song;* @see com.nulldreams.media.manager.ruler.Rule#next(Song, List, boolean);*/public void dispatch(final Song song) { Log.v(TAG, "dispatch song=" + song); Log.v(TAG, "dispatch getAudioFocus mService=" + mService); if (mCurrentList == null || mCurrentList.isEmpty()) {return; } //mCurrentAlbum = null; if (mService != null) {if (song == null && mSong == null) {Song defaultSong = mPlayRule.next(song, mCurrentList, false);dispatch(defaultSong);} else if (song.equals(mSong)) {if (mService.isStarted()) {//Do really this action by userpause();} else if (mService.isPaused()){resume();} else {mService.releasePlayer();if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == requestAudioFocus()) {mSong = song;mService.startPlayer(song.getPath());}}} else {mService.releasePlayer();if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == requestAudioFocus()) {mSong = song;mService.startPlayer(song.getPath());}} } else {Log.v(TAG, "dispatch mService == null");mSong = song;bindPlayService();startPlayService(); }}/*** dispatch the current song*/public void dispatch () { dispatch(mSong);}
这个dispatch方法中,会根据播放状态和当前正在进行的歌曲,判断是否开始播放,暂停还是恢复播放。
在这个过程中,还涉及到获取音频焦点AudioFocus,只有当获取到了音频焦点,再开始播放,获取AudioFocus代码如下:
private int requestAudioFocus () { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); Log.v(TAG, "requestAudioFocus by "); return audioManager.requestAudioFocus(mAfListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);}private int releaseAudioFocus () { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); Log.v(TAG, "releaseAudioFocus by "); return audioManager.abandonAudioFocus(mAfListener);}
当失去音频焦点的时候,我们可以进行以下处理:
private AudioManager.OnAudioFocusChangeListener mAfListener = new AudioManager.OnAudioFocusChangeListener() { @Override public void onAudioFocusChange(int focusChange) {Log.v(TAG, "onAudioFocusChange = " + focusChange);if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT ||focusChange == AudioManager.AUDIOFOCUS_LOSS) {if (isPlaying()) {pause(false);}} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {if (isPaused() && !isPausedByUser()) {resume();}} }};
失去AudioFocus的时候,我们暂停播放;重新获得AudioFocus的时候,判断是否为用户主动暂停,若不是主动暂停,则恢复播放。
/*** resume play*/public void resume () { if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == requestAudioFocus()) {mService.resumePlayer(); }}/*** pause a playing song by user action*/public void pause () { pause(true);}/*** pause a playing song* @param isPausedByUser false if triggered by {@link AudioManager#AUDIOFOCUS_LOSS} or*{@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}*/private void pause (boolean isPausedByUser) { mService.pausePlayer(); this.isPausedByUser = isPausedByUser;}
其他相关的用户控制方法,如上一曲,下一曲等:
/*** next song by user action*/public void next() { next(true);}/*** next song triggered by {@link #onStateChanged(int)} and {@link PlayService#STATE_COMPLETED}* @param isUserAction*/private void next(boolean isUserAction) { dispatch(mPlayRule.next(mSong, mCurrentList, isUserAction));}/*** previous song by user action*/public void previous () { previous(true);}private void previous (boolean isUserAction) { dispatch(mPlayRule.previous(mSong, mCurrentList, isUserAction));}
其中涉及到的mPlayRule,指上一曲下一曲的规则,例如单曲循环、列表循环、随机播放等。库中提供了这样一个接口Rule来实现播放规则。
public interface Rule {Song previous (Song song, List<Song> songList, boolean isUserAction);Song next(Song song, List<Song> songList, boolean isUserAction);void clear ();}
同时内置了单曲循环、列表循环、随机播放三种播放规则,可以通过Rulers使用这三种规则。
处理意外情况的方式所谓意外状况包括插拔耳机与突然来电,这些处理都可以用一个BroadcastReceiver来处理。只需要这个BroadcastReceiver监听。
private SimpleBroadcastReceiver mNoisyReceiver = new SimpleBroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) {if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {// Pause the playbackpause(false);} }};private void registerNoisyReceiver () { mNoisyReceiver.register(mContext, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));}private void unregisterNoisyReceiver () { mNoisyReceiver.unregister(mContext);}
其中的SimpleBroadcastReceiver简单处理了一下,避免重复注册或者未注册即注销时候产生的崩溃。拔出耳机跟收到来电用这个来处理,就可以了,不需监听拔出耳机和来电,所以说,任何企图获取你电话权限的音乐播放应用,肯定不是为了更好的提供音乐服务,只是为了获取更多隐私。
实现远程控制与PlayService保活Notification远程控制与保活由于安卓系统对于系统资源的一些控制,导致即便是耗时任务放在Service中进行,也不能确保在放置于后台后,能一定存活。这就需要我们使用一些方式确保播放后台一直存活下去。最直接的方式,就是通过Service的startForground方法,去显示一个ONGOING的Notification。
需要注意的是,即便是这样做了,也不能100%确定保活,在Nexus设备上测试没有问题,但是在MIUI V8 的小米手机4上,会被杀死,杀死的概率与网易云音乐基本差不多
PlayManager中已经做了相关的逻辑处理,不过如果要自定义样式,则需要你设置一个NotificationAgent,通过这个接口,返回一个supportV7包中的NotificationCompat.Builder。
public interface NotificationAgent {/*** custom your notification style* @param context* @param manager* @param state* @param song* @return*/NotificationCompat.Builder getBuilder (Context context, PlayManager manager, @PlayService.State int state, Song song);/*** you can recycle a bitmap in this method after the notification is already shown*/void afterNotify();}
具体可以参考示例程序中的SimpleAgent类。对于Notification的删除处理,默认的方式是:
Kitkat版本以上(不包括Kitkat),暂停播放后,直接滑动删除,PlayManager就可以释放播放;
Kitkat版本以下(包括Kitkat),右上角显示一个x号,点击直接停止并释放播放。
在SimpleAgent中,使用了MediaStyle能够完美适配各种定制系统,并且配合之后的锁屏控制十分方便。
锁屏控制锁屏控制的关键类是MediaSessionCompat,另外还有两个类十分关键MediaMetadataCompat和 PlaybackStateCompat。
通过MediaMetadataCompat设置锁屏中显示的歌曲信息,例如歌曲名称、歌手名称、专辑、专辑封面等;通过PlaybackStateCompat可以设置锁屏的操作,例如上一曲、下一曲、暂停、恢复播放等。
文末Android 提供了常见的音频、视频的编码、解码机制。更多Android音视频学习进阶,可以私信:发送“核心笔记”或“手册”,即可获取!借助于多媒体类 MediaPlayer 的支持,开发人员可以很方便地在应用中播放音频、视频。只不过使用 MediaPlayer 播放视频时,没有提供图像输出界面。
