视频直播项目

moment.js

官方文档

问题1:切换国家地区

切换国际环境,需要引入对应的国家文件。它默认自带英文环境。

javascript 复制代码
import moment from 'moment'
import 'moment/locale/zh-cn.js'
moment.locale('zh-cn')

音视频播放

问题1:拉流(音视频的容器格式)

android支持: flv

ios支持:m3u8

问题2:视频播放问题

1、无交互的视频自动播放:需要静音。

2、有交互的但是延时播放的策略:

背景:为了视频达到秒开又不浪费流量,开发会在一个图层里面同时渲染三个直播间(每个直播间带有一个视频播放器),分别为上、中、下直播间,中间的直播间是正常播放的,上和下直播间是暂停的。当用户切换直播间的时候,下一个直播间播放视频,如果是无声音则是能够自动播放的,但是有声音的播放在ios上则不行。通过测试可以知道通过交互,在移动端ios上video.play()方法需要在交互事件的0.9S内触发才行,android则可以更长。

解决方案:通过调研与测试,发现:当通过交互让一个有声音的视频播放后再暂停,后续就可以通过异步的方法成功触发video.play()。参考资料

3、组件重新激活 或者 熄灭屏幕重新唤起 视频都会自动暂停,需要调用video.play()方法让视频继续播放。

sdk接入遇到的问题

火山引擎

问题1:拉流失败没有重新拉取

视频拉流过程中,会出现卡住不再播放问题,需要根据sdk提供的错误码,监听当错误出现时,重新调用sdk方法强制去拉流解决这个问题。

webview问题

H5在webview打开后遇到的一系列问题。

问题1:权限

h5在webview上能请求的权限就两个,相机与麦克风。mdn介绍

js 复制代码
// js请求 权限的代码, 权限只能在localhost 或者 域名上才生效,
// ip地址访问会报错
navigator.mediaDevices
  .getUserMedia({ audio: true, video: true })
  .then(function(stream) {
    /* 使用这个 stream stream */
  })
  .catch(function(err) {
    console.error(err)
  })

浏览器上的权限获取流程:h5 -> 浏览器 -> 系统 -> 用户

webview上的权限获取流程:h5 -> webview -> native(app) -> 系统 -> 用户

需要注意的地方则是:h5在webview上获取权限是需要native的研发同学支持的,native的研发同学通过监听拦截到h5的权限请求,再进行赋予操作,否则h5是无法调用权限相关的方法的。

还有一个地方:h5在webview android上是不能一次性请求相机与麦克风权限的,现阶段在webview_flutter android上一次性获取,它那边只能监听拦截到麦克风权限。故此 js 需要分两次去获取,一次视频、一次音频。

问题2:组件input.type='file'

html 复制代码
<input type="file" accept="*">

这个控件在浏览器上点击的时候会自动获取照相机和文件读取权限,不需要用户授权,但是在webview上是需要向系统获取照相机权限的,否则只能进行文件读取操作,故此解决方案则是,H5判断环境,如果是webview打开的H5,在触发这个控件前,需要触发照相机权限。

js 复制代码
navigator.mediaDevices
  .getUserMedia({ audio: false, video: true })
  .then(function(stream) {
    /* 使用这个 stream stream */
  })
  .catch(function(err) {
    console.error(err)
  })

问题3: google登录问题

webview上不能使用google登录,只能通过桥接方法,让app进行google登录。

优雅的桥接方式

传统的桥接方式:

css 复制代码
1、h5通过调native注册的方法把数据传递给native
2、native通过调h5在window上注册的方法把数据传给h5

新的桥接方式:

java 复制代码
native只注册一个controler,
h5通过postMessage的方式告诉native事件名称、传递的数据与回调函数(需要执行的JS代码),
native通过回调函数把数据传给h5。

将h5请求native端的数据以类似发送请求的形式获取

大概思路:

  1. native与h5约定以postMessage方式桥接,方式名为flutter_methods
  2. 约定传递的JSON数据格式,并要求native端如果有数据需要回传则通过callback回传。
json 复制代码
{
    "event_name": "事件名称",
    "data": "事件所传递的数据",
    "callback": "为native需要执行的js代码,通过它将native数据传递给h5"
}
  1. 将桥接方式进行封装并命名为flutter_methods,并返回一个promise,pormise包含了桥接后的数据。
js 复制代码
export const flutter_methods = async ({
  event_name,
  data,
}, isNeedDataFromNative) => {
  return await new Promise((resolve, reject) => {
    if (window?.flutter_methods) {
      window?.flutter_methods.postMessage(JSON.stringify({
        event_name,
        data,
        callback: isNeedDataFromNative ? registerCallback(resolve) : ''
      }))
      if (!isNeedDataFromNative) {
        resolve('')
      }
    } else {
      reject(new Error('get data from native error!'))
    }
  })
}
  1. 声明一个函数方法registerCallback(接受一个reslove函数),该函数方法会在window.knCallbacks[callbackId]上声明一个函数方法,该函数方法会接收native传递过来的数据,再通过执行reslove函数将数据传递给封装的桥接方法flutter_methods。
js 复制代码
function makeRandomId(func) {
  return `${func.name || 'anonymous'}_${Date.now()}`;
}

export const registerCallback = (resolve, keepAlive) => {
  if (!callback) {
    return null;
  }

  const callbackId = makeRandomId(callback);
  window.knCallbacks[callbackId] = (dataFromNative) => {
    let result;
    if (typeof dataFromNative === 'object') {
      result = dataFromNative;
    } else {
      try {
        result = JSON.parse(dataFromNative);
      } catch (e) {
        result = dataFromNative;
      }
    }
    callback(result);
    if (!keepAlive) {
      delete window.knCallbacks[callbackId];
    }
  };

  return `knCallbacks.${callbackId}`;
}

总代码

js 复制代码
// 封装
function makeRandomId(func) {
  return `${func.name || 'anonymous'}_${Date.now()}`;
}

export const registerCallback = (resolve, keepAlive) => {
  if (!callback) {
    return null;
  }

  const callbackId = makeRandomId(callback);
  window.knCallbacks[callbackId] = (dataFromNative) => {
    let result;
    if (typeof dataFromNative === 'object') {
      result = dataFromNative;
    } else {
      try {
        result = JSON.parse(dataFromNative);
      } catch (e) {
        result = dataFromNative;
      }
    }
    resolve(result);
    if (!keepAlive) {
      delete window.knCallbacks[callbackId];
    }
  };

  return `knCallbacks.${callbackId}`;
}

export const flutter_methods = async ({
  event_name,
  data,
}, isNeedDataFromNative) => {
  return await new Promise((resolve, reject) => {
    if (window?.flutter_methods) {
      window?.flutter_methods.postMessage(JSON.stringify({
        event_name,
        data,
        callback: isNeedDataFromNative ? registerCallback(resolve) : ''
      }))
      if (!isNeedDataFromNative) {
        resolve('')
      }
    } else {
      reject(new Error('get data from native error!'))
    }
  })
}

// 业务执行
flutter_methods({
        event_name: 'googleLogin',
      }, true).then((data: FlutterGoogleMsg) => {
        console.log(data)
        if (data.success !== '1') {
          return
        }
        handleLoginByGoogle(data.idToken)
      }).catch((err) => {
        console.error(err)
      }).finally(() => {
        dispatch(setIsShowLoginLoading(false))
      })

白屏问题

背景:webview打开H5的时候,会有一段时间的白屏。

解决方案:

  1. 各种页面与组件的懒加载、预加载
  2. 图片cdn缓存
  3. 骨架屏(loading图)

其中最明显的优化效果就是骨架屏了,在index.html上写入一个loading的div元素(注意不要写在id=root的div上),然后在框架的根组件上监听生命周期,待监听到根组件渲染完成后,隐藏这个loading元素。

google上架问题

挂羊头,卖狗肉。狗肉则是H5。

实现的逻辑是:先上一版正常的APP(卖的是羊头),后上一版非正常的APP(卖狗肉)。后一版APP打开的时候会弹出一个webview,这个webview的url是服务返回的。

app发请求的时候会将广告渠道的标识通过两次BASE64加密后传输。如果是非广告进来(google审核人员)所下载的APP,则服务会根据标识返回隐私页的HTML内容。如果是广告渠道(目标用户)所下载的APP,服务则会返回302状态码,并将实际H5内容放在header里面,让浏览器重定向,从而实现规避google审查。

注意核心点:

  1. app的测试不能有google服务。
  2. app不能用if else 等逻辑判断。
  3. app的桥接使用flutter的插件库实现。
  4. app的google登录使用flutter的插件库实现。
  5. h5所使用到的权限,APP都需要有对应的功能。
  6. app用flutter开发是因为google的审核对flutter没有那么智能,不会因为调用栈问题直接锁死,导致代码也上不了。

内存泄漏问题

根据浏览器的垃圾回收原理:当作用域消失后,其作用域所使用的内存将会被回收。

起因是在flutter里面,webview所打开的h5在观看视频的过程中,超过了十几分钟就崩溃了。一开始是想用传统的方法用google浏览器提供的 google浏览器-更多工具-开发者工具-内存进行记录的,但是它录制不了很长的时间,超过五分钟就不行,如果用五分钟去录制又判断不出究竟有没有内存泄漏。后面换了一种方法 google浏览器-更多工具-任务管理器

看了十几分钟,发现在直播间里面,它的内存占用空间与JS使用的内存会一直上升,从而确定自己的H5代码出现内存泄漏了。(注意:用这个看的时候最好不要打开google的开发者工具,它会导致任务管理器提供的信息很杂)

测试代码

通过blob进行图片上传

通过裁剪工具裁剪完成后获取到的blob是一个地址,则时候如果要获取blob所对应的file,需要走本地接口

js 复制代码
// 举例:blob:http://192.168.28.38:8000/202f8d0a-af47-41de-adce-36e2b878b059
export const getFileFromBlob = (
  file,
  type = 'image/jpeg',
  quality = 0.5,
) => {
  const fileName = moment().unix() + '.jpeg'
  return new Promise((resolve, reject) => {
    fetch(file)
      .then(response => response.blob())
      .then(blobData => {
      // 创建一个 File 对象
        const file = new File([blobData], fileName, { type });

        // 现在,'file' 就是表示文件的 File 对象
        resolve(file);
      })
      .catch(error => reject(error));
  })
}

// 这时候获取到file 上传文件的话,需要上传二进制数据,这时候file转成二进制数据再上传接口
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(currentFile)
fileReader.onloadend = async function () {
  const binaryData = fileReader.result
  setIsLoading(true)
  try {
    let { data } = await Apis.baseUploadImage(binaryData)
    dispatch(
      updateUserInfo({
        avatar: data?.url,
      }),
    )
    setData(val => ({
      ...val,
      avatar: data?.url,
    }))
  } catch (error) {
    console.log('error', error)
  }
  setIsLoading(false)
}

android设备上进行swiper切换问题

背景:swiper中的swiper-slide滑动切换的时候,如果滑动的区域刚好有滚动元素,则会出现滑动异常问题,如下:

解决方法: 给滚动元素绑定touchuStart、touchMove、touchEnd事件,touchuStart时禁止swiper滚动,并记录滑动的x位置,touchMove时获取此时的x与记录的x之间的差值,如大于一定数值,则调用方法滚动swiper-slide。

相关推荐
黄毛火烧雪下33 分钟前
React 深入学习理解
前端·学习·react.js
自不量力的A同学1 小时前
如何下载和安装Firefox 134.0?
前端·firefox
@_猿来如此2 小时前
Web网页制作之JavaScript的应用
前端·javascript·css·html·html5
顾尘眠6 小时前
http常用状态码(204,304, 404, 504,502)含义
前端
王先生技术栈8 小时前
思维导图,Android版本实现
java·前端
悠悠:)9 小时前
前端 动图方案
前端
星陈~9 小时前
检测electron打包文件 app.asar
前端·vue.js·electron
Aatroox9 小时前
基于 Nuxt3 + Obsidian 搭建个人博客
前端·node.js
每天都要进步哦9 小时前
Node.js中的fs模块:文件与目录操作(写入、读取、复制、移动、删除、重命名等)
前端·javascript·node.js
brzhang10 小时前
开源了一个 Super Copy Coder ,0 成本实现视觉搞转提示词,效率炸裂
前端·人工智能