360 前端星 END

11 小程序

小程序:小程序是借助 Web 的开发方式和技术栈,通过宿主 APP 加快渲染,并将 APP 能力有效对外释放的一系列方案的总称

  • 简介
  • 技术架构
  • 开发发布流程
  • 小程序发展

简介

小程序解决的问题

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

小程序技术栈

  • HTML/CSS/JavaScript
  • NodeJS
  • 移动端适配
  • HTTP/HTTPS
  • OAuth2
  • Git

类似小程序的技术

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

小程序技术架构

双线程模式

渲染层:Webview

逻辑层:JsCore

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

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

在这里插入图片描述

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

自动化测试

  • Selenium
  • Puppeteer

12 点播直播

  • 什么是视频
  • Web 端 API
  • Web 端点播直播&播放方案

什么是视频

  • 格式与内容
  • 视频数据
  • 音频数据
  • 传输协议
  • 播放器原理

内容与格式

  1. 文件扩展名≈媒体封装格式(媒体容器类型)
  2. 媒体封装格式≠音视频编码格式(使用了谁家的编码器)
  3. 文件内容:
    1. 头信息(格式、时长、帧率、码率、分辨率…)
    2. 索引信息
    3. 视频数据
    4. 音频数据
    5. 附加增强数据…

视频数据

  1. 显示器颜色呈现基于RGB(红绿蓝)颜色空间模型

  2. 视频领域大多基于YUV颜色空间做抽样存储

在这里插入图片描述

  1. 帧内预测&帧间预测复用进一步有效的压缩数据

  2. P帧(前向预测帧)、B帧(双向预测帧)、I帧(参考帧)

  3. 基于通用标准集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. 点播传输
      • HTTP(S):通过Range方式或参数方式完成Seek
  2. Web端
    • HTTP(S)、WS(S)、P2P…

播放器通用原理

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

Web 端 API

  • 媒体兼容判断
  • 交互式视频
  • 播放本地视频文件
  • 播放硬件资源
  • 实现视频录制
  • 播放 JS 拉取的媒体数据

兼容判断

video 标签的 canPlayType 方法

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);
});
// "mp4: maybe"
// "MP4: probably"
// "webm: probably"
// "ogg: probably"
// "m3u8: 不支持"
// "ts: 不支持"

进度控制实现交互视频

链接

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);
}

本地加载文件播放

链接

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 调用摄像头

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' : '';
};

人脸识别

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 localVideo = document.querySelector('video');
const localCanvas = document.querySelector('canvas');

function poll() {
  const w = localVideo.videoWidth;
  const h = localVideo.videoHeight;
  const canvas = document.createElement('canvas');
  canvas.width  = w;
  canvas.height = h;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(localVideo, 0, 0, w, h);
  // ccv.js 来自 https://github.com/liuliu/ccv/tree/unstable/js
  const comp = ccv.detect_objects({
    canvas: ccv.grayscale(canvas),
    cascade: cascade,
    interval: 5,
    min_neighbors: 1
  });
  /* draw detected area */
  localCanvas.width = localVideo.clientWidth;
  localCanvas.height = localVideo.clientHeight;

  const ctx2 = localCanvas.getContext('2d');
  ctx2.lineWidth = 2;
  ctx2.lineJoin = 'round';
  ctx2.clearRect (0, 0, localCanvas.width,localCanvas.height);

  let x_offset = 0, y_offset = 0, x_scale = 1, y_scale = 1;
  if (localVideo.clientWidth * localVideo.videoHeight > localVideo.videoWidth * localVideo.clientHeight) {
    x_offset = (localVideo.clientWidth - localVideo.clientHeight * localVideo.videoWidth / localVideo.videoHeight) / 2;
  } else {
    y_offset = (localVideo.clientHeight - localVideo.clientWidth * localVideo.videoHeight / localVideo.videoWidth) / 2;
  }
  x_scale = (localVideo.clientWidth - x_offset * 2) / localVideo.videoWidth;
  y_scale = (localVideo.clientHeight - y_offset * 2) / localVideo.videoHeight;

  for (let i = 0; i < comp.length; i++) {
    comp[i].x = comp[i].x * x_scale + x_offset;
    comp[i].y = comp[i].y * y_scale + y_offset;
    comp[i].width = comp[i].width * x_scale;
    comp[i].height = comp[i].height * y_scale;

    let opacity = 0.1;
    if (comp[i].confidence > 0) {
      opacity += comp[i].confidence / 10;
      if (opacity > 1.0) opacity = 1.0;
    }

    //ctx2.strokeStyle = 'rgba(255,0,0,' + opacity * 255 + ')';
    ctx2.lineWidth = opacity * 10;
    ctx2.strokeStyle = 'rgb(255,0,0)';
    ctx2.strokeRect(comp[i].x, comp[i].y, comp[i].width, comp[i].height);
  }
  setTimeout(poll, 500);
}

document.querySelector('button').onclick = () => getUserMediaPromise({ video:true }).then(
  stream => {
    localVideo.srcObject = stream;
    localVideo.onplay = poll;
  },
  error => alert('Failed to get access to local media. Error code was ' + error.code + '.')
);

实时录像

链接

基于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分发 <=> 观众
  1. 媒体类型的选择
  • HTTP(S)-MP4…
    点播服务
  • HTTP(S)-FLV
    点播、直播
  • HTTP(S)-HLS
    点播、直播(高延迟)

播放解决方案

  1. 原生浏览器支持的(能用原生尽量用原生)
    • 直接走原生Video播放
  2. 原生浏览器不支持的
    1. 协议或容器类型不支持
      • JS解协议下载数据、解容器、重新封装,然后通过MSE喂给Video解码、渲染播放
        例如Web端播放FLV、HLS:http://chimee.org
    2. 解码器不支持
      • JS下载数据,WASM 解容器、解码,通过 WebGL&WebAudio 渲染播放
        例如Web端播放HEVC编码视频:https://zyun.360.cn/developer/doc?did=QHWWPlayer
    3. 有解密需求的
      • 参考前两条,在解容器之后对每帧数据启用解密逻辑。

学习资料

  • 基础API

  • [数据获取](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/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 示例

13 代码自我修养

WTF/min——Robert C.Martin

好代码每分钟的抱怨数少

好代码的要求

  • 代码规范

  • 代码格式

  • 流程化

代码规范

ESlint

  • no-fallthrough(质量问题)
  • max-depth(风格问题)
yarn global add 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 和 lint-staged 结合使用,配合 ESlint

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

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

配置好后,在提交后就会自动检查代码问题

代码格式

工具:prettier

流程化

Gitlab Flow

如何优雅提交代码

  • git commit message 规范

  • 合并提交

comments 规范
yarn global add commitizen cz-conventional-changelog standard-version
commitizen init cz-conventional-changelog --yarn --dev --exact

安装工具后,在 commit 时会出现交互提示,根据规范和引导设置 comment

合并提交

希望一个 Feature 只有一个 Commit(但实际上可能一个 Feature 要很多天完成),多个 Commit 会污染 Git logs

 git rebase -i $GIT_LOG_VERSION$

在这里插入图片描述

rebase 有一定危险,多人协作慎用

如果改变了代码的时间线,push 时需要带上 -f

配置总结

.eslintrc.js

module.exports = {
  env: {
    browser: true,
    es6: true,
  },
  extends: ['standard', 'plugin:prettier/recommended'],
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  rules: {
    'no-console': ['error'],
  },
}

.prettierrc

{
  "printWidth": 150,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "insertPragma": false,
  "eslintIntegration": true,
  "jsxBracketSameLine": true
}

package.json

{
  "name": "75start-project",
  "version": "1.2.0",
  "description": "code maintain",
  "main": "index.js",
  "scripts": {
    "commit": "npx git-cz",
    "release": "standard-version",
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "src/**/*.js": [
      "eslint --fix --ext .js",
      "prettier --write",
      "git add"
    ]
  },
  "author": "sunlei",
  "license": "MIT",
  "devDependencies": {
    "cz-conventional-changelog": "3.1.0",
    "eslint": "^6.8.0",
    "eslint-config-prettier": "^6.10.0",
    "eslint-config-standard": "^14.1.1",
    "eslint-plugin-import": "^2.20.1",
    "eslint-plugin-node": "^11.0.0",
    "eslint-plugin-prettier": "^3.1.2",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.1",
    "husky": "^4.2.3",
    "lint-staged": "^10.0.8",
    "prettier": "^2.0.1",
    "pretty-quick": "^2.0.1"
  },
  "husky": {
    "hooks": {
      "pre-commit": "pretty-quick --staged"
    }
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
}

14 技术翻译:进阶的直梯

翻译的意义:阅读外文文档有助于提升

翻译类型

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

技术翻译的意义

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

技术翻译的标准

准确、地道、简洁

文学翻译:信、达、雅

技术翻译的方法

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

为什么不能直译

You can never tell: 你永远不知道

Don’t give that: 少来这套

Talk to your hand: 懒得理你

Ask for the moon: 异想天开

My hands are tired: 我无能为力

I’ll take a rain check: 能不能改天

You have my word: 我保证

技术翻译要坚持技术驱动

发布了10 篇原创文章 · 获赞 8 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/pznavbypte/article/details/105472356
end
360
今日推荐