Flutter代码开发规范

一、目录

目录结构请查看:项目目录结构规范

二、文件+类命名

1、类文件命名

查看dart源码发现,源文件名都是小写英文加上下划线组成,如app_bar.dart;

使用小写加下划线来命名库和源文件

1
2
3
4
5
// good 
app_bar.dart

// bad
AppBar.dart

2、资源文件命名

命名:功能名称 _ 控件类型 _ 自定义参数*(标准或者大字版).png

1
2
标准:share_btn_weixin.png
选中:share_btn_weixin_selected.png

三、类

1、class命名

一般情况下,类名是源文件名的大驼峰写法,如源文件app_bar.dart,类名AppBar;源文件bottom_app_bar,类名BottomAppBar;

1
2
3
class AppBar {

}

2、import

2.1、import顺序

为了使你的文件前言保持整洁,我们有规定的命令,指示应该出现在其中。每个“部分”应该用空行分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
// ①dart库
import 'dart:async';
import 'dart:html';

// ②三方库
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

// ③项目文件
import 'package:my_package/util.dart';

// ④相对文件
export 'src/error.dart';

2.2、import as

使用小写加下划线来命名导入前缀

1
2
3
4
5
6
7
8
9
// good
import 'dart:math' as math;
import 'package:angular_components/angular_components'
as angular_components;

// bad
import 'dart:math' as Math;
import 'package:angular_components/angular_components'
as angularComponents;

3、构造函数

3.1、构造函数定义

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
class Point {
num x, y;

// good
Point2(this.x, this.y);

// bad
Point2(num x, num y) {
this.x = x;
this.y = y;
}
}

3.2、构造函数设计技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

class WishPublishPage extends BJHBasePage {
final bool isUpdateBusiness;
final WishDetailModel wishDetailModel;
final bool reEditPublish;
final Function backCallBack;// 从哪个页面跳转过来的 用于区分游戏中心的跳转

WishPublishPage({
Key key,
this.isUpdateBusiness = false,
this.reEditPublish = false,
this.wishDetailModel,
this.backCallBack,
}) : super(key: key);

WishPublishPage.fromOtherUser({
Key key,
this.reEditPublish = false,
this.isUpdateBusiness = false,
this.wishDetailModel,
List<WishGood> selectGoodsList,
this.backCallBack,
}) : super(key: key) {
wishDetailModel.wishGoods = selectGoodsList; // 暂时只平移代码,不修改(原本可能有问题)
}

@override
_WishPublishPageState createState() => _WishPublishPageState();
}

四、其他命名

1、常量、变量命名

使用小驼峰法命名

1.1、常量

1
2
3
const num pi = 3.14;
const int defaultTimeout = 1000;
final RegExp urlScheme = RegExp('^([a-z]+)

1.2、变量

1
2
3
List<String> bookList;
Map<String, dynamic> bookMap;
bool isShowDetail; // 使用is开头+具体的业务场景命名

非特殊需要,禁止使用dynamic。(无法发挥空安全优势)

1
2
3
4
5
6
7
8
9
10
// 错误代码
dynamic string1 = sp.getString();
if ( string1.length == 2 )

// bugly报错
string1 出现 null

// 正确修改
String? string1 = sp.getString();
if ( string1 != null && string1!.length == 2 )

1.3、枚举命名

1
2
3
4
5
/* 枚举的用途 */
enum Direction {
top, //类型的用途 xxxx
left,//类型的用途 xxxx
}

五、基础接口使用规范

判空(不使用length)

1
2
3
4
5
6
7
// good: isEmpty/isNotEmpty
if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

// bad:length
if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');

代码编写技巧

一、泛型

T是一个抽象类型,因此无法直接使用T(a: "hello")来创建实例。我们需要稍微修改一下代码,以便在test()方法中正确创建T的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
abstract class A {
String? a;
A({this.a});
}

class A1 extends A {
A1({String? a}) : super(a: a);
}

class A2 extends A {
A2({String? a}) : super(a: a);
}

class B<T extends A> {
// 生成泛型T的实例ben。即达到B<A1>().test(); 输出A1,而B<A2>().test(); 输出A2
T createInstance() {
if (T == A1) {
return A1(a: "hello") as T;
} else if (T == A2) {
return A2(a: "world") as T;
} else {
throw Exception("Unsupported type");
}
}

void test() {
T bean = createInstance();
print(bean.runtimeType); // 输出实例的运行时类型
}
}

void main() {
B<A1>().test(); // 输出: A1
B<A2>().test(); // 输出: A2
}

End

APM选型-2备选筛选

产品选型流程通常包含以下步骤:

  1. 确定需求:明确产品选型的目的和需求,包括所需功能、性能、规格、成本等方面的要求。
  2. 调研市场:了解市场上已有的产品和解决方案,分析其特点、优劣势和市场占有率等。
  3. 制定评估标准:根据需求和市场调研结果,制定产品选型的评估标准,包括技术指标、产品性能、价格、服务等方面的标准。
  4. 筛选备选方案:根据评估标准,筛选出符合要求的备选方案,并进行初步比较和评估。
  5. 进行实地考察:对备选方案进行实地考察,包括参观厂家、现场考察、试用产品等。
  6. 编制报告:根据实地考察和评估结果,编制产品选型报告,包括备选方案的优缺点、技术指标、价格、服务等方面的比较和评估结果。
  7. 决策和采购:根据产品选型报告,进行决策和采购,选定最终方案,并进行合同签订、交货、安装、调试等工作。
  8. 跟踪和评估:对选定的产品进行跟踪和评估,及时发现和解决问题,提高产品使用效率和维护质量。

一、比对准备

1、同类型比较

为保证数据准确性,所有的比较必须在同一环境下进行处理。相关的环境因为有时间、平台、版本、灰度、采样率。

序号 因素 影响量 正确方法 错误原因/举例
1 时间 采集的总设备数 记录数据从同一时刻 一个早上、一个下午
2 平台 采集的总设备数 记录数据用同一平台 一个iOS、一个Android
3 版本 sdk的集成时间 记录数据从同一版本后 有些版本只有一个sdk
4 灰度 采集的设备量 同一台手机同时开 两台:一台sdk1,一台sdk2
5 采样率 采集的设备量 两个平台的采样率一样 火山引擎某些指标可设自定义采样率

二、比对因素与实施步骤考虑

1、比对因素

序号 因素 其他
1 两者比较:数据准确性
2 两者比较:数据详细性比较
3 独立功能:功能说明+价值性说明+示例数据(概念说明)
4 两者比较:费用情况,区分短期、长期

2、实施步骤

1、罗列比较因素表。

2、观测并在上述表中记录数据

(每一天:iOS、Android)

三、数据因素项支持情况比对

四、数据因素值比对结果

1、环境值(前提)

以下数据的比对基于相同环境因素,其值如下:

序号 类型 火山 听云 Bugly
1 时间/范围 2023.06.29 10:00:00 –
2023.06.30 10:00:00 (共计1天)
同左 同左
2 平台 iOS 同左 同左
3 版本 V1.x.x 同左 同左
4 灰度 10% 同左 同左
5 采样率 100% 同左 同左

2、结果值(观测数据)–优先级:共有-独有、价值高低

2.1、崩溃、卡顿/anr、错误率

2.1.1、崩溃
类型 火山 听云 Bugly
崩溃次数
崩溃影响设备数
崩溃率
2.1.2、卡顿/anr
类型 火山 听云 Bugly
anr次数
anr影响设备数
anr率
2.1.3、错误率
类型 火山 听云 Bugly
错误率
错误1的次数
错误2的次数
错误3的次数

2.2、启动体验

2.2.1、启动次数
类型 火山 听云 Bugly
启动次数 不支持
首次启动次数 不支持
冷启动次数 不支持
热启动次数 不支持
2.2.2、启动耗时
类型 火山 听云 Bugly
启动平均耗时 不支持
首次启动平均耗时 不支持
冷启动平均耗时 不支持
热启动平均耗时 不支持
2.2.2.1、概念说明

参考文档:xxx

概念简述:

2.3、页面体验

类型 火山 听云 Bugly
页面加载时长 不支持
页面首帧 不支持
页面首屏 不支持
页面FPS 不支持
。。。。。。 不支持

2.4、用户数据

类型 火山 听云
新增用户数
。。。。。。

End

UI组件选择历程

前言

对于组件定制(非从0开发),现有业内有Bruno和Templates等成套组件库,其中多为个人或团队开发,少为公司或企业开源;

组件的实现,有使用多个业内公认性强的独立组件和使用成套组件开发两种方式。

一、现有UI规范与三方UI组件库比较

与三方的比较相似度:

1:不一致,可定制成UI样式,无影响

2:不一致,不可定制成UI样式,

编号 类型 现有UI规范图 三方UI组件图 相似度 是否已实现 备注
1 Loading image-20220112191111893 image-20220112193119354 0% 已实现
2 Toast image-20220112193921648 image-20220112193810225 100% 已实现
3 Alert/Dialog image-20220112190638017
image-20220112191225610
image-20220112192144651
image-20220112193013294
10% 已实现
4 ActionSheet image-20220112191823819 image-20220112193433345 40% 已实现
5 ItemPicker image-20220112191918224
image-20220112192433517
image-20220112192909758 60% 已实现
6 DatePicker image-20220112191956752 image-20220112192754849 60% 已实现
7 AreaPicker image-20220112192520814 image-20220112193655738 0% 已实现
8 Button image-20220112195209438
image-20220112195319946
image-20220112194425751
image-20220112194521899
95% 已实现

基于,目前app中对应的UI组件,基本已实现过,只是缺少统一,导致有复制代码,局部定制的不当操作;

又由于避免重复开发,统一组件,是必须处理的事项。所以,当前的处理方案为抽离并保持已实现的UI,新开发部分采用从组件中调用。此工作与后续其他部分不重合。

而后续如有其他需要定制成组件的部分,对UI实现的图,继续优先与第三方组件库从匹配度、扩展性、维护性等角度考虑。

如果从数据上比价,得知该三方UI组件库与现有UI匹配度较低(匹配度=符合规范的组件个数/总的组件个数)。

基于匹配度较低的情况,处理方案有如下两种。

方案一:UI规范向三方组件靠齐,即后续UI设计使用三方组件样式;

方案二:放弃三方组件库,分别使用独立三方组件,定制成现有UI想要的特有样式;

二、UI规范选择方案比较

方案一:
使用三方规范
(由组件库集体定制)
方案二:
使用自己规范
(由独立组件分别定制)
前期开发效率
美观性
前提 舍弃部分美观性,需领导批准,同步UI团队 正常开发

1、开发优先,放弃特有风格

如果从未开发,则基于前期开发效率考虑,使用方案一成套组件库进行开发(需领导批准,同步UI团队改变设计规范)。

2、风格保持,实现次之

如果为了保证维持app特有的风格,组件实现则必须与UI设计保持一致,则

1、三方成套组件库与UI匹配度高:

直接使用

2、三方成套组件库与UI匹配度低:

则组件的实现,有以下两种方式

①尝试基于成套组件库,进行深度定制,如无法定制指定效果,放弃;

②使用与UI匹配度高的独立三方组件,进行快速定制;

三、保持UI风格使用自己规范时,开源库选择标准

成套组件库 VS 各独立组件,我们主要从以下三点考虑:

一、后期维护性。维护性是避免该组件不进行维护,导致需要使用新的组件进行替换。从概览上讲成套组件的不维护风险比由多个独立组件组成的集体不维护风险概览大,从而产生的维护替换成本也会较高。

二、扩展性。扩展性是指该组件支持的可定制程度。为了避免有些组件早高度封装下,可定制程度不高,当后期需要补充定制其他功能时候,无法支撑需求,从而导致需要修改源码或使用新组件来替换,我们会优先考虑扩展性强的组件,从而节省后期的维护成本。

三、受众广度。指一个组件或一群组件组成的组件库在业内使用开发者的多少或流行度。以下以轻提示控件toast举例,单个toast和组件库里的toast,在https://pub.flutter-io.cn/上查到的受众广度分别如下:

toast-fluttertoast

toast-Bruno

对于项目中前期已实现的组件,是优先以使用github上与我们匹配最高且Star最多的组件来进行二次封装,从而定制成App所需的实际样式。如无或只是简单封装,才使用一次封装和定制,为的是避免对只是简单封装的控件也产生不必要的依赖。就开发成本上来讲,与使用成套组件无实际差别,并不会多增加,且有降低成套组件后续维护性风险的好处。

四、后续

基于目前从产品、UI上已统一使用Bruno中的风格,所以后续对于前期未实现的组件(如后续可能需要的底部弹窗下拉关闭组件)会直接使用Bruno组件库中的组件;而前期已实现的部分,在产品和UI有需要更新风格的情况下,也会按需进行替换成组件库中的风格

End

加固选型-1调研

参考文档:

腾讯云加固功能介绍-官网

一、确定需求

1、痛点/所需功能/期望

序号 影响/目的 所需功能
1
2

2、预算成本

二、调研市场

了解市场上已有的产品和解决方案,分析其特点、优劣势和市场占有率等。

1、调研包含内容/考虑点

序号 考虑项/因素 内容 其他
1 包含的功能 ①必须/主要功能
②补充功能
2 后台数据 控制台数据呈现、维度
3 收费标准 免费功能、免费额度、免费门槛

2、调研产品列表

厂家 产品 官网 支持列表
360 360加固 https://jiagu.360.cn/#/global/vip/desc
腾讯 腾讯乐固 https://cloud.tencent.com/document/product/283/14002

3、调研包含内容/考虑点对比表

三、制定评估标准

将APM产品的功能清单转化为具体的评估指标,并根据用户需求和期望,给予不同指标不同的权重。例如,对于监控指标,可以考虑启动时间、页面加载时间、卡顿率、崩溃率等方面,对于诊断指标,可以考虑问题定位时间、错误率、准确性等方面。

序号 类型 功能【含Flutter上的支持】 影响/目的 需求程度(1-10)
1

根据上述评估标准,我们将《XXX》和《XXX》列入备选方案。

四、筛选备选方案、实地数据对比

根据评估标准,筛选出符合要求的备选方案,并进行初步比较和评估。

1、筛选方案,详见:

2、筛选方案下的实地数据对比,详见:

五、选型决策和采购

六、跟踪和评估

End

FlutterGetX框架选择历程

为什么

前言

1.getX的功能不只是只有状态管理功能。
2.只是为了状态管理的话,项目中新功能可使用。使用不影响其他人。且目前项目中的依赖已包含getX。
3.如果是旧功能,在赶项目的阶段如果没法把握能保量保质的完成开发,不建议使用。当然还是希望大家使用,但任务的完成质量是第一位,如果只是为了使用而影响了项目进度,我认为不是特别可取,除非涉及严重问题不得不。所以没有规定不能使用,但结果是要完成好开发,而不是简单的“好了”,然后存在有很多细节没完善的地方。

对于一个功能,我认为所谓的好了,起码是
①主流程没问题,
②特殊流程已处理,
③异常已容错,
④界面效果与ui完全一致,不差任何一两个像素及颜色偏差,
⑤使用上至少不存在用户感知到的卡顿,
⑥自己发现的问题已跟进处理,而不是挂着等测试或产品提,
⑦别人发现的也一样处理,而不是不重要,
⑧甚至不应该出现用户感知是可点击的,结果点了了没任何效果的问题,哪个加个toast也比没有强
⑨流程上的有些偏可以下沉到底层的,如按钮的重复点击问题处理,图片的加载缓存404等处理,是否在自己的模块上处理完成,或者说告知之前这块没处理过,后面安排处理,
⑩debug模式是否正常,而不是像项目一开始时候的debug都基本无法debug,都在跑release。
等等…

对内的代码结构是否合理,

所以在能保证质量完成开发或者除了不影响使用的解决不了的情况下很希望大家也多看看多学学多了解和使用一些优秀的框架。但框架是为开发服务的,不能说想使用了什么框架结果完成不了,或做得有点大概。
4.另外对于getX的状态管理使用,也不是一使用了就得全部替换,甚至说新功能就一定得按getX来实现。拿个关于页面,一个简单页面用getX与现有的,反而使用getX可认为是完全没必要,因为页面可认为是不需要“可变”状态,只是需要很独立的数据显示而已。类似于做原生,也有很多优秀的框架甚至架构,但mvc的模式肯定在你的项目里有存在,而且在那种场景下,它的优点反而更明显。可以理解为越简单的东西越没有必要使用非常强大的框架,没必要用大炮打小鸟。

优秀新框架的初使用,我认为在前期是允许使用,而不是规定都得使用。好比有人很熟悉,但其他人不熟悉可能做起来,他就没法完成他原本安排的任务,形象而夸大的说,可以想象成让你换个语言开发,排期不变,保证开发完成和质量,而且还不是说他那块的就一定得使用新框架才能。

所以鼓励使用优秀的技术,但别忘了保证完成自己的开发。当然使用新技术时候,如果时间差不多,也可同产品商量适当一两天,在其接受延期的情况下才能延期,不然要使用的话,哪怕加再多班也得完成。而对于一些类似完全重构的任务,自然会同产品协商专门安排一期处理。不管是原生开发还是跨平台,我觉得这些理念和思维应该都差不多。

End

技术选型流程

一、选型流程

产品选型流程通常包含以下步骤:

  1. 确定需求:明确产品选型的目的和需求,包括所需功能、性能、规格、成本等方面的要求。

    在确定需求的过程中,需要明确用户的痛点、需求和期望,以及应用场景和系统规模等因素。

  2. 调研市场:了解市场上已有的产品和解决方案,分析其特点、优劣势和市场占有率等。

  3. 制定评估标准:根据需求和市场调研结果,制定产品选型的评估标准,包括技术指标、产品性能、价格、服务等方面的标准。

    制定评估指标和权重:将APM产品的功能清单转化为具体的评估指标,并根据用户需求和期望,给予不同指标不同的权重。例如,对于监控指标,可以考虑启动时间、页面加载时间、卡顿率、崩溃率等方面,对于诊断指标,可以考虑问题定位时间、错误率、准确性等方面。

  4. 筛选备选方案:根据评估标准,筛选出符合要求的备选方案,并进行初步比较和评估。

  5. 进行实地考察:对备选方案进行实地考察,包括参观厂家、现场考察、试用产品等。

  6. 编制报告:根据实地考察和评估结果,编制产品选型报告,包括备选方案的优缺点、技术指标、价格、服务等方面的比较和评估结果。

  7. 决策和采购:根据产品选型报告,进行决策和采购,选定最终方案,并进行合同签订、交货、安装、调试等工作。

  8. 跟踪和评估:对选定的产品进行跟踪和评估,及时发现和解决问题,提高产品使用效率和维护质量。

End

切为H5的标准

针对指示的h5实现方式和空态页面体验优化问题,我们经讨论,处理方案如下

问题一:对目前app中存在的H5页面,哪些是需要处理成h5的,初步定为如下标准:

1、黄金主流程的必须保证flutter实现,更稳定;

2、对于强运营的、改动量比较频繁的采用h5,一般为活动页;

3、对于纯展示交互少的采用h5,如隐私协议、玩法说明等;

按以上标准,目前app中,需要处理成h5页面的有:

状态1:已由H5实现,无需修改;

状态2:待开发,直接开发成H5;

状态3:原生实现,后续优化时调整为H5;

1、注册协议、隐私政策、玩法说明:待开发,直接开发成H5;

2、频道页:目前只有潮物是h5,其他已用flutter实现,后续待频道页优化时调整为H5;

3、活动规则(百愿清单、愿望星规则等):目前已有部分由flutter实现,后续逐步调整为H5;

4、大转盘、动物运动会、4大场景(生日、节日、结婚、生娃):已由H5实现;

5、首页banner跳转的落地页:跳转百愿清单、愿望单为黄金主流程已由flutter实现;跳转电子邀请函、动物运动会已由H5实现;

问题二:对app中无数据的空态页面太空的体验优化问题,晚上产品会把所有页面都列出来,再跟您对一遍,然后我们再统一做修改。

方案陈述以上,如有描述不当或需要再补充的,感谢指点!

视频优化

前言

一、视频上传

文件分片上传与分片下载.md

二、常见视频播放

1、视频地址/素材获取网站

mov_website1

2、m3u8文件认识

m3u8 是 HLS 协议中使用的播放列表文件格式,它是 M3U 文件格式的扩展,专门用于 HLS。

在 HLS(HTTP Live Streaming)中,主播放列表(Master Playlist)媒体播放列表(Media Playlist) 通常是 分开的两个文件,而不是合并到一个文件中。这种分离的设计是为了支持自适应码率切换和多分辨率适配。

HLS 的 m3u8 文件有两种主要类型:

主播放列表.m3u8(Master Playlist)用于描述多个不同码率(分辨率)的视频流,每个流对应一个独立的媒体播放列表 .m3u8 文件。它是一个”目录”,不包含实际的 TS 片段,只包含指向不同分辨率/码率的子播放列表的链接。

一般命名为: master.m3u8index.m3u8 (包含 EXT-X-STREAM-INF

媒体播放列表.m3u8(Media Playlist)一定会提供。用于描述一个视频流的具体分段(切片)信息,每个分段通常是一个 .ts 文件。

一般命名为:low/index.m3u8mid/index.m3u8high/index.m3u8 (包含 EXTINF.ts

在某些情况下,可能会直接提供媒体播放列表给 App,而不是主播放列表。例如:单一码率:如果视频只有一种码率,可能会直接提供媒体播放列表。不然正常情况下一般提供一个主播放列表(Master Playlist)文件,然后播放器根据网络条件自动选择最合适的媒体播放列表。

  • 主播放列表媒体播放列表 的文件后缀都是 .m3u8
  • 区分它们的关键在于文件内容:
    • 主播放列表包含 #EXT-X-STREAM-INF 标签,描述多个视频流。
    • 媒体播放列表包含 #EXTINF 标签,描述一个视频流的分段信息。
  • 文件后缀相同是为了保持格式统一和简化解析。

现在比较常见的视频流媒体,大部分都是 m3u8 格式的,而对于 m3u8 格式的视频而言,如果你下载过,你会发现它就是一个文本文件,大概也就只有几十 kb,从磁盘大小来看,应该也知道它并不是一个直接的视频文件。即如果我们想要下载对应的视频文件,直接下载 m3u8,当然是达不到目的的。

m3u8 是一种视频的播放格式,它是将一个完整的视频切割成多个 ts 后缀的视频,然后当我们的进度条被移动或者按时间顺序移动的时候,就会下载对应的片段来加载。

触发的方法在iOS中如下,其中 loadingRequest 每一小块数据的请求,请求地址如m3u8格式中的ts文件链接地址。

1
2
3
4
5
6
7
8
9
10
/*  连接视频播放和视频断点下载的桥梁
* 必须返回Yes,如果返回NO,则resourceLoader将会加载出现故障的数据
* 这里会出现很多个loadingRequest请求,需要为每一次请求作出处理
* 该接口会被调用多次,请求不同片段的视频数据,应当保存这些请求,在请求的数据全部响应完毕才销毁该请求
* @param resourceLoader 资源管理器
* @param loadingRequest 每一小块数据的请求
*/
- (BOOL)resourceLoader:(AVAssetResourceLoader*)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest*)loadingRequest{
// TODO:在这里面开始我们的网络下载请求,也就是得到AVAssetResourceLoadingRequest对象
}

实现代码摘自:KJPlayerDemo 其说明文档见: iOS 音频视频播放器实现边下载边播放缓存视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// 开始请求下载数据 
NS_INLINE void kStartDownloading(KJDownloader * downloader, AVAssetResourceLoadingRequest * request){
AVAssetResourceLoadingDataRequest *dataRequest = request.dataRequest;
NSInteger length = dataRequest.requestedLength;

NSInteger offset;
if (dataRequest.currentOffset != 0) {
offset = (NSInteger)dataRequest.currentOffset;
} else {
offset = (NSInteger)dataRequest.requestedOffset;
}
if (@available(iOS 9.0, *)) {
if (dataRequest.requestsAllDataToEndOfResource) {
[downloader kj_downloadTaskRange:NSMakeRange(offset, length) whole:YES];
return;
}
}
[downloader kj_downloadTaskRange:NSMakeRange(offset, length) whole:NO];
}

2.1、m3u8文件示例

多码率自适应流示例(Master Playlist)

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000,RESOLUTION=720x480
low/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000,RESOLUTION=1280x720
mid/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5120000,AVERAGE-BANDWIDTH=4000000,RESOLUTION=1920x1080
high/video.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=10240000,AVERAGE-BANDWIDTH=8000000,RESOLUTION=3840x2160
uhd/video.m3u8

m3u8文件示例1:ts片段是完整地址

1
2
3
4
5
6
7
8
9
10
11
#EXTM3U
#EXT-X-TARGETDURATION:19
#EXT-X-VERSION:2
#EXTINF:10,
http://122.225.31.106/6775ED76AE94E84581E2FA4D03/0300080B0053D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=0&ts_end=10&ts_seg_no=0
#EXTINF:10,
http://122.225.31.106/6775ED76AE94E84581E2FA4D03/0300080B0053D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=10&ts_end=20&ts_seg_no=1
…………..
#EXTINF:12,
http://122.225.31.85/657DD4BEC15488401753C7200C/0300080B0A53D05A49A22808DF700EC607AF80-D3EF-E08A-FB37-918809D8CF4C.mp4.ts?ts_start=320&ts_end=336&ts_seg_no=396
#EXT-X-ENDLIST

m3u8文件示例2:这里的片段,全部是基于域名的相对地址,

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:2
#EXT-X-MEDIA-SEQUENCE:102
#EXT-X-TARGETDURATION:12
#EXTINF:10,
57b3f432.ts
#EXTINF:12,
57b3f43c.ts
#EXTINF:9,
57b3f446.ts

2.2、m3u8文件字段说明

#EXTINF:10表示的是这段TS的时长是10秒。

57b3f432.ts这里表示的是每一个TS的文件名,有的M3U8这里直接是一个完成的http链接。

其他参考文章:

iOS解析M3U8文件及TS文件下载与合并

iOS 拼接m3u8文件 苹果m3u8怎么转换成mp4

1
self.playerUrl = @"http://cctv2.vtime.cntv.wscdns.com:8000/live/no/204_/seg0/index.m3u8?begintime=1469509516000";

附:边下边播

1、解码m3u8:拿到一个M3U8链接后可以解析出M3U8索引的具体内容,包括每一个TS的下载链接、时长等;

下载:拿到每一个TS文件的链接就可以逐个下载了,下载后存储为xxx.ts到手机里;
打包:将下载的TS数据按照播放顺序打包,供客户端播放;
播放:数据打包完成,就可以播放了。

Android——实现m3u8视频缓存

iOS流媒体开发之三:HLS直播(M3U8)回看和下载功能的实现

iOS 视频边下边播(缓存,预加载)

点播:

服务器将直播内容使用FFMPEG转码成MP4和3GP等点播源,生成播放连接返回给客户端播放就可以了。

2、播放器选择之路

音视频播放对于现在的互联网应用来说,已经是不可或缺的功能之一。作为一个 App 开发者,开发一个音视频播放功能,说难不难,说简单也不简单,我们常常会面临几个抉择:

  1. 使用原生视频组件(如:MediaPlayer)
  2. 使用原生硬解码/FFmpeg软解,定制视频播放组件
  3. 使用完全开源的第三方组件(如:ijkplayer)
  4. 使用商业第三方组件(如:腾讯云播放器,阿里云播放器)

2.1、腾讯云播放器

flutter_tencentplayer_plus

三、直播礼物播放

直播礼物类型详解

VAP动效实现方案

SVGA VS VAP

SVGA 是矢量动画方案,对简单动画支持更好。

VAP是视频方法,对复杂动画支持更好。相比SVGA,VAP具有更高的压缩率和硬件解码的优势,使得动画播放更加流畅。VAP的素材大小较小,仅为1.5M,远小于Apng和Webp格式,且采用硬件解码,解码速度更快。

VAP在文件大小和内存占用方面优于SVGA,这可以极大的节约文件包的空间并加快用户的读取速度

四、视频预加载

上中下三个player组成类似轮播的效果。播放当前视频时候,预加载下一个视频的数据。

视频播放延迟

由于分段和缓冲机制,HLS的延迟通常高于实时流媒体协议(如RTMP)。HLS的延迟有多少,怎么优化延迟

1. HLS延迟的来源

HLS延迟主要由以下几个因素引起:

  • 分段长度:HLS将视频分割成多个小片段(通常为10秒),播放器需要等待第一个完整的片段下载完成后才能开始播放。
  • 缓冲机制:播放器通常会预加载多个片段以应对网络波动,这会增加延迟(实际是并发的加载影响了第一个片段的加载时间)。

缓冲机制会预加载多个片段,为的是播放的流畅性。延迟的来源是第一个片段什么时候加载完。但由于有缓冲机制所以会同时加载第一个片段和后续的片段,所以假设是3个片段缓冲,则第一个片段在这种并发的情况相爱可能是最后才加载完的。所以 HLS延迟的典型值

  • 默认配置:10秒片段长度 + 3个片段缓冲 = 30秒左右延迟
  • 优化配置(减少片段长度+减少缓冲区个数):2秒片段长度 + 1个片段缓冲 = 3秒到5秒延迟

五、视频帧

上下两个区域。页面下部分区域为视频的帧图,页面上部分为视频滑动到指定位置显示的画面,以此来形成滑动页面下部分滚动条时候,能实时看到视频帧的效果,实际最终帧为点击确认后才截取。

业务流程图:视频帧获取.graffle

六、视频播放暗掉

核心:

1
2
3
4
5
// 1.打开常亮
[[UIApplication sharedApplication] setIdleTimerDisabled:YES] ;

// 2.关闭长亮
[[UIApplication sharedApplication] setIdleTimerDisabled:NO] ;

视频解码

音频降噪

【岗位核心价值】
通过技术手段实现沉浸式短剧播放体验,推动产品在海外市场的用户增长与留存。
【岗位职责】
负责短剧App的iOS原生模块开发及Flutter跨平台功能实现
优化视频流加载、多分辨率适配及低延迟播放技术方案
实现海外支付(Google Pay/Apple Pay/本地钱包)、多语言动态切换等全球化功能构建AB测试框架支持海外市场本地化运营
持续优化应用性能,确保百万级DAU下的稳定性
【技术要求】
必备能力:
3年以上iOS原生开发经验,精通Swift/
Obiective-0
,2年以上Flutter商业项目经验,熟悉Dart语言特性
深刻理解MVC/MVVM架构,具备模块化开发能
熟悉App上架流程及海外合规要求(GDPR/COPPA)
加分项:
熟悉Android开发及Flutter与Native混合开发
有出海项目经验(AWS/GCP部署、多CDN调度)

枚举值名称 枚举值 枚举类型描述
V2TIM_ELEM_TYPE_NONE 0 没有元素
V2TIM_ELEM_TYPE_TEXT 1 文本消息 V2TimTextElem
V2TIM_ELEM_TYPE_CUSTOM 2 自定义消息 V2TimCustomElem
V2TIM_ELEM_TYPE_IMAGE 3 图片消息 V2TimImageElem
V2TIM_ELEM_TYPE_SOUND 4 语音消息 V2TimSoundElem
V2TIM_ELEM_TYPE_VIDEO 5 视频消息 V2TimVideoElem
V2TIM_ELEM_TYPE_FILE 6 文件消息 V2TimFileElem
V2TIM_ELEM_TYPE_LOCATION 7 地理位置消息 V2TimLocationElem
V2TIM_ELEM_TYPE_FACE 8 表情消息 V2TimFaceElem
V2TIM_ELEM_TYPE_GROUP_TIPS 9 群 Tips 消息(存消息列表) V2TimGroupTipsElem
V2TIM_ELEM_TYPE_MERGER 10 合并消息 V2TimMergerElem

End

依赖关系可视化

参考文档:

一、cocoapods依赖关系可视化

cocoapods管理的三方库之间有时候存在依赖关系,Podfile.lock藏满了各个版本库的版本号信息和彼此的依赖关系。

1、网页可视化导出:cocoapods-graph

终端安装插件:

1
pip install cocoapods-graph

使用方式:

进入到工程的根目录,执行:

1
2
3
cd ~/Project/CQComponent/UIKit-Overlay-iOS/TSOverlayDemo

cocoapods-graph -f Podfile.lock --html

生成 Podfile.lock.html 文件,

cocoapods-graph-01

其内容展示如下:

cocoapods-graph-02

2、图片可视化导出:cocoapods-dependencies

终端安装插件:

1
2
gem install cocoapods-dependencies
brew install graphviz

使用方式:

进入到工程的根目录,执行:

1
pod dependencies [PODSPEC] [--graphviz] [--image]
  • pod 是 CocoaPods 的命令行工具。
  • dependencies 是 CocoaPods 中的一个命令,用于显示特定 pod 或项目中所有 pod 的依赖关系。
  • [PODSPEC] 是一个可选参数,用于指定特定 pod 的名称。如果你提供了 pod 的名称,它将显示该 pod 的依赖关系。如果你没有提供 pod 的名称,它将显示项目中所有 pod 的依赖关系。
  • --graphviz 是一个可选标志,它会生成一个 Graphviz DOT 文件(一般为Podfile.gv ),表示 pod 的依赖关系图。这个文件可以用于可视化依赖关系。
  • --image 是一个可选标志,它会从 Graphviz DOT 文件生成一个图像文件(一般为Podfile.png )。这样你就可以以图像格式查看依赖关系图。

示例1:不提供 [PODSPEC] 将显示项目中所有 pod 的依赖关系。

1
2
3
4
5
6
cd ~/Project/CQComponent/UIKit-Overlay-iOS/TSOverlayDemo

pod install

# 不提供 [PODSPEC] 将显示项目中所有 pod 的依赖关系。
pod dependencies --graphviz --image

生成 Podfile.gvPodfile.png文件如下:

cocoapods-dependencies-01-html

其中 Podfile.gv 可用 OmniGraffle应用程序 打开

gv的打开方式

打开方式说明如下:

序号 打开方式 打开说明 示例
1 Hieracchical 层次结构
2 Force-Directed 强制导向
3 Circular 圆形的
4 Radial 径向的

其各自详细效果图,详见 依赖关系可视化-TSOverlayDemo.graffle

示例2:提供 [PODSPEC] 将显示该 pod 的依赖关系

命令:pod dependencies AFNetworking --graphviz --image

这个命令将显示 AFNetworking 这个 pod 的依赖关系,并生成一个 Graphviz DOT 文件和相应的图像文件,用于可视化查看 AFNetworking 的依赖关系。你可以在生成的图像文件中查看依赖关系图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1、安装
gem install reversepoddependency

# 2、查找所安装库里的命令路径
gem which reversepoddependency
# gem which:这是一个用于定位 Ruby gem 安装路径的命令。当你执行 gem which GEM_NAME 时,它会返回指定 gem 的安装路径。
# which:这是一个用于查找可执行文件的命令。
# 得到的结果如下:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/lib/reversepoddependency.rb
# 所以该包的 specbackwarddependency 应在如下目录:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/
# 经查找 specbackwarddependency 的其实际地址为如下:
# /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency

# 3、使用所安装库里的命令
/usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking

在运行 gem install reversepoddependency 命令后,安装的 reversepoddependency gem 应该会被存储在你使用的 Ruby 安装目录下的 Gems 文件夹中。

问题:执行 /usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking 时候出现如下错误:

specbackwarddependency_01

我们通过进入指定文件/usr/local/lib/ruby/gems/3.2.0/gems/cocoapods-core-1.12.1/lib/cocoapods-core/source.rb 查看其210行,并添加以下代码,即可过滤掉该库:

1
2
3
4
if name == "TUICalling" || name == "TUIChat" || name == "VK-ios-sdk" 
name = "AFNetworking"
version = "3.2.1"
end

specbackwarddependency_02

不然你得通过以下命令找到该所在库,然后再一个个删掉,这种方式会造成后续使用问题。所以我们选择修改判断代码

1
2
3
4
cd ~/.cocoapods/repos/trunk/Specs

# 查找指定库
find ~/.cocoapods/repos/trunk/Specs -name 'TUICalling'

添加代码后,重新执行

1
/usr/local/lib/ruby/gems/3.2.0/gems/reversepoddependency-0.1.0/exe/specbackwarddependency ~/.cocoapods/repos/trunk AFNetworking

得到的结果如下:

specbackwarddependency_03

End

项目目录结构规范实践

步骤

1、项目目录结构分类与创建

2、

一、项目目录结构分类与创建

1、utils
2、models
3、commons

1、独立类(utils\models\common)的文件归类、迁移

1.1、库创建(0.5h)

1.2、文件归类与迁移(4h)

1、util

2、models

用户

序号 类型
1 用户
2 内容
3

1.3、内部的依赖修复与优化

内部引用优化为使用相对路径或新的绝对路径。

1
import './a.dart';

1.4、外部的使用优化

优化为

Step1:内部 export 各种文件

1
2
export './src/a.dart';
export './src/b.dart';

Step2:外部 import 基础类库的总文件

1
2
3
import 'package:app_utils/app_utils.dart';
import 'package:app_models/app_models.dart';
import 'package:app_common/app_common.dart';

2、通用组件类(commons)文件归类、迁移

1、内含适配组件 adapt

每个视图都需要依赖

2、基础用户头像、商品视图

依赖:基础的像素适配

入参:关键性参数及自定义的点击回调

附:升级版通用功能组件类common_ui(如用户头像、商品视图等),见下文。

3、基础路由(route)

4、升级版通用功能组件类common_ui(如用户头像、商品视图等)

以用户头像举例:

①需要支持传入baseUserModel的时候,显示头像信息

②需要支持传入baseUserModel的时候,点击跳转到对应用户详情页

所以需要依赖的有:

序号 需求 需要依赖 其他
1 需要支持传入baseUserModel的时候,显示头像信息 models
2 需要支持传入baseUserModel的时候,点击跳转到对应用户详情页 route

商品视图:

基础类空安全升级