2020-04-12

##小程序,大世界

###简介

· 小程序解决了什么问题

· 小程序技术栈

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库进⾏行行交互

###小程序的技术架构

• ⽂文件结构及其含义

.json 后缀的 JSON 配置⽂文件

.wxml 后缀的 WXML 模板⽂文件

.wxss 后缀的 WXSS 样式⽂文件

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

⽂文件结构及其含义:JSON

⽂文件结构及其含义:WXML

本质是HTML模版

有特定的标签

接管⼀一些简单的逻辑判断

JS不不直接操作DOM,只负责set数据

⽂文件结构及其含义: WXSS

提供rpx单位

精简的CSS

提供全局和局部的CSS

⽂文件结构及其含义: JS

负责逻辑交互

APP 、Page、Component三个构造函数

可调⽤用系统API

• 双线程模型

• 生命周期

• 组件

• 其他

插件机制

云端函数

⼩小游戏

###开发发布流程

开发者在⼩小程序平台注册⼩小程序,以获得APPID

初始化代码并完成代码仓库配置

开发代码并调试

上传并发布

###小程序的发展

多端同构框架

⾃自动化

硬件框架

云IDE

W3C⼩小程序⼯工作组

同构框架

意义:⼀一次编写适配多端,⼀一次迭代各端同步

利利⽤用Web的优点,以及对各个平台进⾏行行动态适配

⼩小程序⾃自动化

控制⼩小程序跳转到指定⻚页⾯面 • 获取⼩小程序⻚页⾯面数据

获取⼩小程序⻚页⾯面元素状态

触发⼩小程序元素绑定事件

往 AppService 注⼊入代码⽚片段 • 调⽤用 wx 对象上任意接⼝口

硬件框架

云IDE

W3C小程序工作组

##Web前端点播直播入门

###什么是视频?

1、格式与内容

· 文件扩展名≈媒体封装格式(媒体容器类型)

· 媒体封装格式≠音视频编码格式(使用了谁家的编码器)

· 文件内容:

1) 头信息(格式、时长、帧率、码率、分辨率...)

2) 索引信息

3) 视频数据

4) 音频数据

5) 附加增强数据...

2、视频数据

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

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

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

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

· 基于通用标准集N多技术于一身 --- 视频编码器

H.264(AVC)、H.265(HEVC)、VP8、VP9...

3、音频数据

· 声音:不同振幅&频率而产生的机械波;数字形式是一维波形

· 对自然中连续的声波采样,做数字化PCM存储

· 扬声器还原PCM(脉冲编码调制)数字信号为模拟音频信号

· 音频压缩基本算法:预测、变换

· 基于通用标准集N多技术于一身 --- 音频编码器​​​​​​​

AAC、MP3...

4、传输协议

传统场景

流媒体(直播)

· HLS:苹果为利用现有CDN设施而发明的"流媒体"协议

· HTTP(S)-FLV:基于HTTP的流媒体协议

· RTMP、RTP/RTSP、TS、MMS...

点播传输

· HTTP(S):通过Range方式或参数方式完成Seek

Web端

·HTTP(S)、WS(S)、P2P...

5、播放器原理

· 解协议(加载数据)

· 解封装(解复用)

· 解码

· 渲染

###好玩的WebAPI

1、媒体兼容判断

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

});

2、交互式视频

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

}

3、播放本地视频文件

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 = '';

  }

}

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

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

};

5、实现视频录制

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

}

6、播放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分发 <=> 观众

媒体类型的选择

· HTTP(S)-MP4.. 点播服务

· HTTP(S)-FLV 点播、直播

· HTTP(S)-HLS 点播、直播(高延迟)

2、播放器解决方案

原生浏览器支持的

· 直接走原生Video播放

原生浏览器不支持的

· 协议或容器类型不支持

JS解协议下载数据、解容器、重新封装,然后通过MSE喂给Video解码、渲染播放

例如Web端播放FLV、HLS:http://chimee.org

· 解码器不支持

JS下载数据,WASM 解容器、解码,通过 WebGL&WebAudio 渲染播放

例如Web端播放HEVC编码视频:https://zyun.360.cn/developer/doc?did=QHWWPlayer

· 有解密需求的

参考前两条,在解容器之后对每帧数据启用解密逻辑。

##前端代码的自我修养

如何衡量代码质量的好坏?

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

代码的自我修养

· 代码规范

· 格式

· 流程化

代码规范:

安装Eslint:yarn global add eslint

使用Eslint规范代码

流程化:

如何优雅地提交代码

· git commit message规范

· 合并提交

##技术翻译:进阶的直梯

1、翻译类型

文学翻译 非文学翻译

艺术成分多一些 科学成分多一些

需要更多的灵感 需要更多的勤奋

责任小一些 责任大一些

2、技术翻译的意义

翻译技术文章,学习新技术思想

翻译技术文档,掌握标准和工具

翻译技术图书,获得名声和报酬

3、技术翻译的标准

准确、地道、简洁

4、技术翻译的方法

消化吸收原文

母语地道表达

就是翻译意思

5、技术翻译要坚持技术驱动

示例原文

An object literal can only use a symbol as a property inside the computed property syntax.

let s1 = Symbol('foo'),

let o = {

  [s1]: 'foo val'

};

// Also valid:

// o[s1] = 'foo val';

console.log(o);

// {Symbol{foo}: foo val}

参考译文

对象字面量只能在计算属性语法中使用符号作为属性。

语法中使用符号作为属性。

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

推荐阅读更多精彩内容