360前端星计划0412

小程序,大世界

1.简介

1.1 小程序解决了什么问题

  • 技术方向
    • H5(白屏时间长,调用系统能力弱)
    • 提高启动速度
      • 本质是数据和展示更彻底的分离
      • 符合数据驱动的前端优势
    • 实现最大限度地宿主能力外放
    • APP(体验好,开发成本高)
    • 开发成本降低
    • 技术统一
    • 多平台展现逻辑一致
    • 跨平台解决方案
  • 非技术方向
    • 审核保证应用合法性(审核后才放心下发更多权限)
    • 平台优势
    • 推广入口
    • 拉活手段
    • 配套工具
      • 微信开发者后台的分析报告

1.2 相关的技术栈

HTML/CSS/JavaScript

  • NodeJS
  • 移动端适配
  • HTTP协议/HTTPS
  • OAuth2
  • GIT

1.3 主流的小程序平台

微信小程序、支付宝小程序、百度小程序、头条小程序、京东小程序、快应用、钉钉小程序、360小程序(基于PC的小程序)

1.4类似小程序的技术

  • Cordova: 通过webview渲染,通过插件调用系统服务
  • PWA:Service Worker和PushAPI
  • React Native/Weex:JavaScript通过JavaScriptCore等执行,并通过Bridges和Native组件交互
  • Flutter:Dart直接与独立系统的UI库进行交互

2.小程序技术架构

2.1文件结构及其含义

.json后缀的JSON配置文件

app.json

  • 职责:小程序文件的配置
  • pages
  • window
    project.config.json
    -职责:微信开发者工具配置
    每个页面下的json
  • 职责:个性化page,可覆盖app.json中window的配置
    sitemao.json
  • 职责:指导搜索引擎进行检索

.wxml后缀的WXML模板文件

  • 本质是HTML模板
  • 有特定的标签
  • 接管一些简单的逻辑判断
  • JS不直接操作DOM,只复制set数据

.wxss后缀的WXSS样式文件

  • 提供rpx单位(屏幕宽度与750的比值)
  • 精简的CSS
  • 提供全局和局部的CSS

.js后缀的JS脚本逻辑文件

  • 负责逻辑交互
  • APP、Page、Component三个构造函数
  • 可调用系统API

2.2 双线程模型

渲染层和逻辑层是两个线程


小程序双线程模型

渲染层不能操作 DOM,降低风险,但同时也造成不便

双线程导致线程通信实现数据传递,会有一定延迟

2.3生命周期

小程序生命周期图

小游戏实时性要求较高,没有采用双线程架构

2.4组件

  • 官方组件
    • 容器
    • 基础内容
    • 表单组件
    • 导航
    • 地图
    • 画布
  • 原生组件
  • 自定义组件
    使用Component构造器,通过usingComponents引入

2.5其他

插件机制

如果想要开发插件,就将开发模式选为插件即可

云端函数

小游戏

3.开发发布流程

  • 开发者在小程序平台注册小程序,以获得APPID(需要用一个没有注册过小程序的邮箱进行注册)
  • 初始化代码并完成代码仓库配置
  • 开发代码并调试
  • 上传并发布

4.小程序的发展

多端同构框架

  • 意义:一次编写适配多端,一次迭代各端同步
  • 利用Web的优点,以及对各个平台进行动态适配
    常用框架:uni-app、Taro、KBone


    KNone的目录结构

自动化

  • 控制小程序跳转到指定页面
  • 获取小程序页面数据
  • 获取小程序页面元素状态
  • 触发小程序元素绑定事件
  • 往AppService注入代码片段
  • 调用wx对象上任意接口

硬件框架

小程序硬件框架

云IDE

W3C小程序工作组

  • W3C小程序和快应用草案
    • URL Schema
    • Widget
    • Manifest
    • Lifestyle
    • Packaging

Web前端点播直播入门

什么是视频

了解媒体数据存储和应用的基础原理

格式与内容

1.文件扩展名≈媒体封装格式(媒体容器类型)
2.媒体封装格式≠音视频编码格式(使用了谁家的编码器)
3.文件内容:

  1. 头信息(格式、时长、帧率、码率、分辨率...)
  2. 索引信息
  3. 视频数据
  4. 音频数据
  5. 附加增强数据(存储一些音视频之外的信息,比如自定义的一些数据)

视频数据

1.显示器颜色呈现基于RGB(红绿蓝)颜色空间模型
2.视频领域大多基于YUV颜色空间做抽样存储


3.帧内预测&帧间预测复用进一步有效的压缩数据
4.P帧(前向预测帧)、B帧(双向预测帧)、I帧(参考帧)
5.基于通用标准集N多技术于一身 --- 视频编码器
H.264(AVC)、H.265(HEVC)、VP8、VP9...

音频数据

1.声音:不同振幅&频率而产生的机械波;数字形式是一维波形
2.对自然中连续的声波采样,做数字化PCM存储
3.扬声器还原PCM(脉冲编码调制)数字信号为模拟音频信号
4.音频压缩基本算法:预测、变换
5.基于通用标准集N多技术于一身 --- 音频编码器​​​​​​​
AAC、MP3...

传输协议

  1. 传统场景
    1.流媒体(直播)
    - HLS:苹果为利
    用现有CDN设施而发明的"流媒体"协议(实际上是基于文件存储的)
    • HTTP(S)-FLV:基于HTTP的流媒体协议
    • RTMP、RTP/RTSP、TS、MMS...
      2.点播传输
    • HTP(S):通过Range方式或参数方式完成Seek
      2.Web端
    • HTTP(S)、WS(S)、P2P...

播放器原理

1.解协议(加载数据)
2.解封装(解复用)
3.解码
4.渲染

好玩的Web端API

了解通过Web端接口可以实现哪些方向的具体应用

媒体兼容判断

let videoEl = document.createElement("video");
let types = {
  'mp4': 'audio/mp4',
  'MP4': 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
  'webm': 'video/webm; codecs="vp8, vorbis"',
  'ogg': 'video/ogg; codecs="theora, vorbis"',
  'm3u8': 'application/vnd.apple.mpegURL',
  'ts': 'video/mp2t; codecs="avc1.42E01E,mp4a.40.2"'
};

Object.keys(types).forEach(key => {
  const type = types[key];
  const ret = videoEl.canPlayType(type) || '不支持';
  console.log(key + ': ' + ret);
});

交互式视频

基于Video时间轴实现交互式视频

let video = $('video');

video.ontimeupdate = ()=>{
  let {currentTime} = video;
  show(currentTime > 64 ? '.s2' : '.s1');
  hide(currentTime > 64 ? '.s1' : '.s2');
  if(
     (currentTime > 64 && currentTime < 65) || 
     (currentTime > 113 && currentTime < 114)
  ){
    video.pause();
  }
};

let ppBtn = $('paly_pause');
video.onplay = ()=>{
  ppBtn.innerText = '暂停';
};
video.onpause = ()=>{
  ppBtn.innerText = '播放';
};
ppBtn.onclick = ()=>{
  video[video.paused ? 'play' : 'pause' ]();
};
$('start').onclick = ()=>{
  video.currentTime = 1;
  video.play();
};
$('step').onclick = ()=>{
  video.currentTime = 60;
  video.play();
};
$('dream').onclick = ()=>{
  video.currentTime = 83;
  video.play();
};
$('drink').onclick = ()=>{
  video.currentTime = 116;
  video.play();
};

hide('.s2');
function show(sel){
  document.querySelectorAll(sel).forEach(el=>{
    el.style.display='inline'
  });
}
function hide(sel){
  document.querySelectorAll(sel).forEach(el=>{
    el.style.display='none'
  });
}
function $(id){
  return document.getElementById(id);
}

播放本地视频文件

基于 FileReader API 播放本地文件

let iptFileEl = document.querySelector('input[type="file"]');
let videoEl = document.querySelector('video');

iptFileEl.onchange = e =>{
  let file = iptFileEl.files && iptFileEl.files[0];
  playFile(file);
};

function playFile(file){
  if(file){
    let fileReader = new FileReader();
    fileReader.onload = evt => {
      if(FileReader.DONE == fileReader.readyState){
        videoEl.src = fileReader.result;
      }else{
        console.log('FileReader Error:', evt);
      }
    }
    fileReader.readAsDataURL(file);
  }else{
    videoEl.src = '';
  }
}

播放硬件资源(调用摄像头或麦克风)

基于 getUserMedia 调用摄像头或麦克风
iframe 引入页要调用媒体设备的,需要父页面中给iframe设置 allow="microphone;camera;midi;encrypted-media;" 。

const getUserMediaPromise = options => new Promise((resolve, reject) => {
  const nvgt = window.navigator;
  if(nvgt) {
    if(nvgt.mediaDevices && nvgt.mediaDevices.getUserMedia) {
      return nvgt.mediaDevices.getUserMedia(options).then(resolve, reject);
    }
    const getUserMedia = nvgt.getUserMedia || nvgt.webkitGetUserMedia || nvgt.mozGetUserMedia;
    if(getUserMedia) {
      return getUserMedia(options, resolve, reject)
    }
  }
  reject('当前环境不支持获取媒体设备。');
});

let streamTrack;
const video = document.querySelector('video');
document.querySelector('#play').onclick = () => {
  getUserMediaPromise({
    audio: false,
    video: true
  }).then(stream => {
    video.srcObject = stream;
    streamTrack = stream.getTracks()[0];
  },
  err => {
    console.log('getUserMedia error: [' + err.name + '] ' + err.message)
  });
};

document.querySelector('#stop').onclick = () => {
  streamTrack && streamTrack.stop();
};

const box = document.querySelector('div');
document.querySelector('#sketch').onclick = () => {
  box.className = box.className ==='' ? 'sketch' : '';
};

人脸识别

实现视频录制

基于 getUserMedia、MediaRecorder 实现录像

const getUserMediaPromise = options => new Promise((resolve, reject) => {
  const nvgt = window.navigator;
  if(nvgt) {
    if(nvgt.mediaDevices && nvgt.mediaDevices.getUserMedia) {
      return nvgt.mediaDevices.getUserMedia(options).then(resolve, reject);
    }
    const getUserMedia = nvgt.getUserMedia || nvgt.webkitGetUserMedia || nvgt.mozGetUserMedia;
    if(getUserMedia) {
      return getUserMedia(options, resolve, reject)
    }
  }
  reject('当前环境不支持获取媒体设备。');
});

const video = document.querySelector('#preview');

let cameraStream;
const opencameraBtn = document.querySelector('#opencamera');
const closecameraBtn = document.querySelector('#closecamera');
const recordBtn = document.querySelector('#record');
const stopRecordBtn = document.querySelector('#stoprecord');
const playBtn = document.querySelector('#play');
const downloadBtn = document.querySelector('#download');

opencameraBtn.onclick = () => getUserMediaPromise({
  audio: false,
  video: true
}).then(
  stream => {
    cameraStream = video.srcObject = stream;
    opencameraBtn.disabled = true;
    closecameraBtn.disabled = false;
    recordBtn.disabled = false;
  },
  err => {
    console.log('getUserMedia error: [' + err.name + '] ' + err.message)
  }
);

closecameraBtn.onclick = () => {
  cameraStream && cameraStream.getTracks()[0].stop();
  cameraStream = null;
  opencameraBtn.disabled = false;
  closecameraBtn.disabled = true;
  stopRecordBtn.onclick();
};

let mediaRecorder;
let recordedBlobs;
const mimeType = ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm', ''].find(type => {
  return MediaRecorder.isTypeSupported(type);
});
// console.log('mimeType', mimeType);
recordBtn.onclick = () => {
  recordedBlobs = [];
  try {
    mediaRecorder = new MediaRecorder(cameraStream, { mimeType });
  } catch(e) {
    alert('Exception while creating MediaRecorder: ' + e + '. mimeType: ' + mimeType);
    return;
  }
  recordBtn.disabled = true;
  stopRecordBtn.disabled = false;
  playBtn.disabled = true;
  downloadBtn.disabled = true;
  mediaRecorder.onstop = evt => {
    console.log('Recorder stopped');
  };
  mediaRecorder.ondataavailable = function(event) {
    if (event.data && event.data.size > 0) {
      recordedBlobs.push(event.data);
    }
  };
  mediaRecorder.start(20); // 单次收集数据毫秒时长,ondataavailable 触发频率时长间隔
};


const recordedVideo = document.querySelector('#recorded');
stopRecordBtn.onclick = () => {
  mediaRecorder && mediaRecorder.stop();
  mediaRecorder = null;
  // console.log('Recorded Blobs: ', recordedBlobs);
  recordedVideo.controls = true;
  playBtn.disabled = false;
  downloadBtn.disabled = false;
  stopRecordBtn.disabled = true;
  if(!cameraStream) {
    recordBtn.disabled = true;
  }
};

const getRecordedBlobUrl = () => {
  const superBuffer = new Blob(recordedBlobs, {type: mimeType.split(';')[0]});
  return window.URL.createObjectURL(superBuffer);
};

playBtn.onclick = () => {
  recordedVideo.src = getRecordedBlobUrl();
}

downloadBtn.onclick = () => {
  var a = document.createElement('a');
  a.style.display = 'none';
  a.href = getRecordedBlobUrl();
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}

播放JS拉取的媒体数据

基于MediaSource播放JS拉取的媒体数据

const video = document.querySelector('video');
const fetchMp4 = (url, cb) => { 
  const xhr = new XMLHttpRequest();
  xhr.open('get', url);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function () {
    cb(xhr.response);
  };
  xhr.send();
};

const assetURL = 'https://nickdesaulniers.github.io/netfix/demo/frag_bunny.mp4';
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';

// 创建动态媒体源,并关联到video元素上
const mediaSource = new MediaSource(); 
video.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  // 拉取数据
  fetchMp4(assetURL, buf => {
    sourceBuffer.addEventListener('updateend', () => {
      // 媒体流传输完毕
      mediaSource.endOfStream();
      // video.play();
    });
    // 将数据喂给 Video -- 注意这里只是一次性输入整个MP4数据
    sourceBuffer.appendBuffer(buf);
  });
});

Web端点播直播&播放方案

点播直播的区别

1.应用流程
- 点播:创作者 => 上传 => 转码 => 存储 <=> CDN分发 <=> 观众
- 直播:创作者 => 推流 <=> 存储 <=> 转码 <=> CDN分发 <=> 观众
2.媒体类型的选择

  • HTTP(S)-MP4..
    点播服务
  • HTTP(S)-FLV
    点播、直播
  • HTTP(S)-HLS
    点播、直播(高延迟)

播放器解决方案

  1. 原生浏览器支持的
    • 直接走原生Video播放
  2. 原生浏览器不支持的
    1. 协议或容器类型不支持
      • JS解协议下载数据、解容器、重新封装,然后通过MSE喂给Video解码、渲染播放
        例如Web端播放FLV、HLS:http://chimee.org
    2. 解码器不支持
    3. 有解密需求的
      • 参考前两条,在解容器之后对每帧数据启用解密逻辑。

可扩展学习的参考资料

基础API:
https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/Using_HTML5_audio_and_video

数据获取:
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia
https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API

虚拟文件:
https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL

动态媒体源:
https://developer.mozilla.org/zh-CN/docs/Web/API/Media_Source_Extensions_API

数据操作:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API

画音渲染:
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API

场景应用:
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder
https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API

开源项目:
http://chimee.org
https://github.com/bilibili/flv.js
https://github.com/video-dev/hls.js
https://github.com/huzunjie/WasmVideoPlayer

非开源 WasmVideoPlayer 示例:
http://lab.pyzy.net/qhww

前端代码的自我修养

如何衡量代码质量的好坏

衡量代码质量的唯一有效标准:WTF/min —— Robert C. Martin


image.png

代码的自我修养

代码规范

  • no-fallthrough(质量问题)
  • max-depth(风格问题)
    ESlint
yarn global add eslint//安装eslint
{
    "extends": "eslint:recommended",
    "rules": {
        // enable additional rules
        "indent": ["error", 4],
        "linebreak-style": ["error", "unix"],
        "quotes": ["error", "double"],
        "semi": ["error", "always"],

        // override default options for rules from base configurations
        "comma-dangle": ["error", "always"],
        "no-cond-assign": ["error", "always"],

        // disable rules from base configurations
        "no-console": "off",
    }

ESlint还可以配合前端框架工作
.eslintrc.js

module.exports = {
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": [
        "standard"
    ],
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "rules": {
        "no-console" : ["error"]
    }
};

husky + lint-staged
husky 原理:修改 .git/hooks/pre-commit

"scripts": {
    "precommit": "lint-staged"
},
"lint-staged":{
    "src/**/*.js": [
        "eslint --fix --ext .js",
        "prettier --write",
        "git add"
    ]
}

完成配置后,再提交代码就会自动检查代码问题

格式

eslint会自动检查代码格式是否有错误
工具:prettier

流程化

image.png

如何优雅地提交代码

git commit message规范

合并提交

有的时候,我们会遇到多次Commit才能完成一个feature
这时git log就会出现多次记录,如下图所示,污染提交历史


image.png

使用以下命令进行合并提交

git rebase -i $GIT_LOG_VERSION$

具体步骤如下

git commit --fixup HEAD

git rebase -i $GIT_LOG_VERSION$ --autosquash

git push -f origin master

rebase 有一定危险,多人协作慎用
如果改变了代码的时间线,push 时需要带上 -f

技术翻译,进阶地直梯

翻译的类型

  • 文学翻译
  • 非文学翻译
    文学翻译| 非文学翻译
    -|-
    艺术成分多一些 |科学成分多一些
    需要更多的灵感 |需要更多的勤奋
    责任小一些 |责任大一些

技术翻译的意义

  • 翻译技术文章,学习新技术思想
  • 翻译技术文档,掌握标准和工具
  • 翻译技术图书,获得名声和报酬

技术翻译的标准

准确、地道、简洁

技术翻译的方法

  • 消化吸收原文
  • 母语地道表达
  • 就是翻译意思

技术翻译要坚持技术驱动

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 综合类 综合类 地址前端知识体系 http://www.cnblogs.com/sb19871023/p/3894...
    飞菲fly阅读 847评论 0 3
  • 迅捷CAD看图软件如何快速更改图纸背景色?CAD图纸文件绘制完成之后一般其背景色都是黑色,那么我们在使用CAD看图...
    周周周大璇阅读 748评论 0 0
  • 一天天,一年年, 母爱在身边! 人在远方心挂念, 盼儿把家还。 回首看,多少年? 沧桑催容颜! 一生操劳无怨言, ...
    李兴元阅读 209评论 1 2
  • 第一次见她,是在15年的6月份,那时候我刚毕业,见到她的第一眼,觉得她的头真小,脸怎么可以那么圆。 我...
    seeumine阅读 175评论 0 0
  • 回忆曾经的幸福,感觉现在已经消失! 1.刚认识的时候,老公会关心我,天冷脱衣服给我穿。 2.老公会做饭给我吃,在网...
    人间百合阅读 274评论 0 1