2022年11月

  1. 新建React Native空白项目
  2. 安装依赖:

    yarn add @react-navigation/bottom-tabs react-native-safe-area-context createBottomTabNavigator
  3. 最简洁的tabbar示例,App.tsx里放置代码:

    import * as React from 'react';
    import { Text, View } from 'react-native';
    import { NavigationContainer } from '@react-navigation/native';
    import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
    
    function HomeScreen() {
      return (
     <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
       <Text>Home!</Text>
     </View>
      );
    }
    
    function SettingsScreen() {
      return (
     <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
       <Text>Settings!</Text>
     </View>
      );
    }
    
    const Tab = createBottomTabNavigator();
    
    export default function App() {
      return (
     <NavigationContainer>
       <Tab.Navigator>
         <Tab.Screen name="Home" component={HomeScreen} />
         <Tab.Screen name="Settings" component={SettingsScreen} />
       </Tab.Navigator>
     </NavigationContainer>
      );
    }

    官方文档:https://reactnavigation.org/docs/tab-based-navigation#minimal-example-of-tab-based-navigation

onChange={(e)=>{
  if (! /^\d?(\d+[\.]?\d*)?$/.test(e.target.value) ) return
  setInputVal(e.target.value)
}}

^\d?表示0个或1个数字开头
\d+表示1个或1个以上数字
[.]?表示0个或者1个小数点
\d*表示0个或0个以上的数字
(\d+[.]?\d*)?表示括号里可以重复0遍或0遍以上次数
(\d+[.]?\d)?$表示以(\d+[.]?\d)?结尾

  1. 新建react native项目

    npx create-expo-app -t expo-template-blank-typescript
  2. 根据教程配置https://github.com/expo/config-plugins/tree/main/packages/ffmpeg-kit-react-native

    1. 安装依赖

      yarn add ffmpeg-kit-react-native @config-plugins/ffmpeg-kit-react-native expo-build-properties

      github教程里没说要装expo-build-properties,导致后续步骤报错

    2. app.json里添加

      "plugins": [
        [
          "@config-plugins/ffmpeg-kit-react-native",
          {
            "package": "full-gpl"
          }
        ]
      ]

      关于full-gpl含义:https://github.com/tanersener/mobile-ffmpeg/releases

  3. 根据上述生成ios和android文件夹

    expo prebuild

    ios/Podfile里的iOS版本太旧会导致编译失败,可根据ffmpeg sdk的官方要求更改版本号:https://www.npmjs.com/package/ffmpeg-kit-react-native

  4. App.tsx编写如下代码

    import { StatusBar } from 'expo-status-bar';
    import { StyleSheet, Text, View } from 'react-native';
    import { FFmpegKit, ReturnCode } from 'ffmpeg-kit-react-native'
    import { useEffect } from 'react';
    import * as FileSystem from 'expo-file-system'
    
    export default function App() {
      useEffect(()=>{
        FFmpegKit.execute(`-i https://static-b905bdbb-5254-4483-af4c-16e5bf477a2e.bspapp.com/mpd/wolf.mpd -c copy ${FileSystem.documentDirectory}/${Math.random()}.mp4`)
        .then(async (session) => {
          const returnCode = await session.getReturnCode()
          if (ReturnCode.isSuccess(returnCode)) {
            console.log('SUCCESS')
          } else if (ReturnCode.isCancel(returnCode)) {
            console.log('CANCEL')
          } else {
            console.log('ERROR')
          }
        })
      }, [])
    
      return (
        <View style={styles.container}>
          <Text>Open up App.tsx to start working on your app!</Text>
          <StatusBar style="auto" />
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
      },
    });
  5. 云端编译并在iOS虚拟机上运行

    yarn ios

    看到SUCCESS的log说明ffmpeg运行成功

import BigNumber from "bignumber.js"
export function bigNumberFloor(num: string|number|BigNumber, decimals: number): string {
  return new BigNumber(num).times(10**decimals).idiv(1).div(10**decimals).toFormat()
}
export function bigNumberCeil(num: string|number|BigNumber, decimals: number): BigNumber {
  return new BigNumber(num).times(10**decimals).integerValue(BigNumber.ROUND_CEIL).div(10**decimals)
}

React Native Webview组件没有自带的方式可以监听网页内部资源的加载(jpg, png, js, css, mp3等),曲线实现方式:用js注入定时器到网页,网页里获取节点的src属性并postMessage给native,以img节点为例:

import { WebView } from 'react-native-webview'
let srcs:string[] = []
export default function WebviewScreen() {
  return (
    <WebView 
      source={{ uri: 'https://www.bing.com/images' }}
      useWebKit={true}
      allowsBackForwardNavigationGestures
      allowsInlineMediaPlayback
      injectedJavaScriptBeforeContentLoaded = {`
        setInterval(()=>{
          const nodes = document.querySelectorAll('img')
          for (const node of nodes) {
            window.ReactNativeWebView.postMessage(node.src)
          }
        }, 2000)
      `}
      onMessage = {msg=>{
        const src = msg.nativeEvent?.data?.trim()
        if (src && -1===srcs.indexOf(src)) {
          console.log('onMessage', new Date(), src)
          srcs.push(src)
        }
      }}
    />
  );
}

也可以用于video等其它带有src的节点
如果是video,src可能在它的子节点srouce里,而且可能有多个,所以需要这么写:

setInterval(() => {
  const videoElems = document.querySelectorAll('video')
  for (const videoElem of videoElems || []) {
    window.ReactNativeWebView?.postMessage(videoElem.src)
    for (const courceElem of videoElem.getElementsByTagName('source') || []) {
      window.ReactNativeWebView?.postMessage(courceElem.src)
    }
  }
}, 1000)

缺点:

  1. 只能定时不能监听
  2. 如果网页里用到跨域的iframe,则iframe里的resource无法被获取