├── README.md ├── app.js ├── app.json ├── app.wxss ├── pages ├── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── pull │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── pullQRCode │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── push │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss └── video │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss ├── project.config.json ├── res └── img │ ├── adapt.png │ ├── anyway.png │ ├── beauty.png │ ├── bg.png │ ├── camera.png │ ├── df_hd.png │ ├── df_sd.png │ ├── df_ssd.png │ ├── err_icon.png │ ├── file.png │ ├── future_icon.png │ ├── logo.png │ ├── mute.png │ ├── pause.png │ ├── play.png │ ├── pull_icon.png │ ├── push_icon.png │ ├── qrcode.png │ ├── scan.png │ ├── share.png │ ├── turnout.png │ ├── vod_icon.png │ └── white.png └── utils └── weapp.qrcode.esm.js /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 网易云信视频直播DEMO微信小程序版本(以下简称视频小程序DEMO)是一套结合网易云信直播点播能力,基于微信小程序平台开发的视频直播点播小程序。此DEMO用于为开发者演示直播推流、直播播放、点播播放等能力。由于微信小程序为第三方平台,使用过程中可能存在某些兼容性问题,建议您综合评估风险。我们推荐您使用云信原生移动端SDK进行开发。 3 | 4 | 视频小程序DEMO可扫描以下二维码体验: 5 | 6 | ![](https://yx-web.nos-hz.163yun.com/offical-site%2F3333.jpg)![](https://yx-web.nos-hz.163yun.com/offical-site%2F2222.jpg) 7 | 8 | 9 | 视频小程序DEMO主要功能列表如下: 10 | 11 | |总览|| 12 | | :------- | :----- | 13 | |功能 |描述| 14 | |功能入口 |直播推流,直播播放,点播播放,更多功能| 15 | |权限要求 |摄像头、录音、用户信息| 16 | 17 | 18 | |直播推流 || 19 | | :------- | :----- | 20 | |功能 |描述| 21 | |获取并填充推流地址 |点击添加,获取一个推流地址进行推流| 22 | |输入推流地址 |支持文本框粘贴输入推流URL| 23 | |二维码扫描获取推流地址 |支持二维码扫描获取推流地址| 24 | |开始/停止推流 || 25 | |美颜 || 26 | |清晰度选择 || 27 | |摄像头切换 || 28 | |摄像头开启/关闭 || 29 | |声音开启/关闭 || 30 | |扫码获取拉流地址 || 31 | 32 | 33 | |直播播放 || 34 | | :------- | :----- | 35 | |功能 |描述| 36 | |粘贴拉流地址 |支持rtmp flv协议拉流,提供文本框供粘贴| 37 | |扫码填充拉流地址 |支持通过扫描二维码填充拉流地址| 38 | |开始/停止播放 || 39 | |支持系统音量调整 |支持通过改变系统音量调节播放声音| 40 | |横竖屏切换 || 41 | 42 | 43 | |点播播放 || 44 | | :------- | :----- | 45 | |粘贴播放地址 |支持mp4,flv,HLS格式播放,提供文本框供粘贴URL| 46 | |填充一个默认播放地址 |点击添加,填充一个默认播放地址,播放准备好的测试视频| 47 | |扫码填充播放地址 |支持通过扫描二维码填充播放地址| 48 | |开始/暂停播放 || 49 | |点播拖拽进度条 |手势拖拽seek到指定位置| 50 | |支持系统音量调整|支持通过改变系统音量调节播放声音| 51 | |横竖屏切换 || 52 | 53 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | App({ 2 | onLaunch: function () {}, 3 | globalData: {} 4 | }) 5 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/push/index", 5 | "pages/pullQRCode/index", 6 | "pages/pull/index", 7 | "pages/video/index" 8 | ], 9 | "window":{ 10 | "navigationBarTitleText": "云信视频DEMO", 11 | "navigationBarBackgroundColor": "#000", 12 | "navigationBarTextStyle": "white", 13 | "backgroundTextStyle": "light", 14 | "backgroundColor": "#f4f4f4", 15 | "enablePullDownRefresh": false, 16 | "onReachBottomDistance": 0 17 | }, 18 | "networkTimeout": { 19 | "request": 10000, 20 | "connectSocket": 10000, 21 | "uploadFile": 10000, 22 | "downloadFile": 10000 23 | }, 24 | "debug": false 25 | } 26 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | body, page { 2 | height: 100%; 3 | } 4 | .f-o-v { 5 | overflow: visible; 6 | } 7 | .f-db { 8 | display: block; 9 | } 10 | .f-disable { 11 | opacity: 0.4; 12 | } 13 | .f-c-red { 14 | color: red; 15 | } 16 | .coverImg { 17 | position: absolute; 18 | } 19 | .page-box { 20 | width: 100%; 21 | height: 100%; 22 | background: #000; 23 | color: #fff; 24 | } 25 | .ipt-box { 26 | position: relative; 27 | width: 100%; 28 | height: 150rpx; 29 | background: rgba(0, 0, 0, 0.4); 30 | } 31 | .ipt { 32 | display: inline-block; 33 | margin: 18rpx 0 18rpx 20rpx; 34 | width: 558rpx; 35 | height: 64rpx; 36 | color: #ffffff; 37 | font-size: 28rpx; 38 | background-color: rgba(0, 0, 0, 0.3); 39 | border-radius: 8rpx; 40 | opacity: 0.6; 41 | } 42 | .placeholder { 43 | opacity: 0.6; 44 | font-size:28rpx; 45 | color:#fff; 46 | } 47 | .btn { 48 | position: absolute; 49 | top: 18rpx; 50 | display: inline-block; 51 | width: 56rpx; 52 | height: 56rpx; 53 | } 54 | .btn-1 { 55 | right: 95rpx; 56 | } 57 | .btn-2 { 58 | right: 20rpx; 59 | } 60 | .desc { 61 | display: block; 62 | padding: 0 0 0 20rpx; 63 | font-size: 24rpx; 64 | opacity: 0.6; 65 | line-height: 24rpx; 66 | } 67 | .video-box { 68 | width: 100%; 69 | } 70 | .video { 71 | width: 100%; 72 | height: 100%; 73 | } 74 | .action { 75 | position: absolute; 76 | right: 0; 77 | bottom: 0; 78 | left: 0; 79 | width: 100%; 80 | height: 300rpx; 81 | background: rgba(0, 0, 0, 0.4); 82 | text-align: center; 83 | font-size: 22rpx; 84 | } 85 | -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | Page({ 2 | onShareAppMessage: function () { 3 | return { 4 | title: '云信视频DEMO', 5 | path: 'pages/index/index', 6 | imageUrl: '/res/img/share.png' 7 | } 8 | } 9 | }) 10 | 11 | -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "云信视频DEMO", 3 | "disableScroll": true 4 | } -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 直播推流 6 | 7 | 8 | 9 | 10 | 11 | 直播播放 12 | 13 | 14 | 15 | 16 | 17 | 点播播放 18 | 19 | 20 | 21 | 22 | 23 | 敬请期待 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 本程序展示音视频能力由网易云信提供技术支持 33 | 34 | 35 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .flex-wrp { 2 | display: flex; 3 | width: 100%; 4 | height: 680rpx; 5 | flex-flow: row wrap; 6 | } 7 | .flex-item { 8 | width: 50%; 9 | height: 50%; 10 | } 11 | .btn-item { 12 | margin-top: 40rpx; 13 | margin-bottom: 40rpx; 14 | padding-bottom: 30rpx; 15 | background: #fff; 16 | border: 0 solid #e3e3e3; 17 | box-shadow: 0 2px 8px 0 rgba(218, 218, 218, 0.5); 18 | border-radius: 8rpx; 19 | text-align: center; 20 | overflow: hidden; 21 | } 22 | .btn-item-1, .btn-item-3 { 23 | margin-right: 20rpx; 24 | margin-left: 40rpx; 25 | } 26 | .btn-item-2, .btn-item-4 { 27 | margin-right: 40rpx; 28 | margin-left: 20rpx; 29 | } 30 | .item-img { 31 | display: block; 32 | margin: 40rpx auto 32rpx; 33 | width: 120rpx; 34 | height: 120rpx; 35 | } 36 | .item-txt { 37 | font-size: 24rpx; 38 | line-height: 34rpx; 39 | color: #333; 40 | } 41 | .item-txt-des { 42 | color: #666; 43 | } 44 | .intro { 45 | position: absolute; 46 | right: 0; 47 | bottom: 0; 48 | left: 0; 49 | height: 136rpx; 50 | text-align: center; 51 | } 52 | .intro-logo { 53 | width: 250rpx; 54 | height: 42rpx; 55 | } 56 | .intro-desc { 57 | font-size: 24rpx; 58 | color: #999; 59 | } -------------------------------------------------------------------------------- /pages/pull/index.js: -------------------------------------------------------------------------------- 1 | const app = getApp() 2 | Page({ 3 | data: { 4 | pulling: false, 5 | livePlayerContext: {}, 6 | src: '', // 音视频地址。目前仅支持 flv, rtmp 格式 7 | mode: 'live', // live(直播),RTC(实时通话) 8 | autoplay: false, 9 | muted: false, 10 | orientation: 'vertical', // 画面方向,可选值有 vertical,horizontal 11 | objectFit: 'contain', // 填充模式,可选值有 contain,fillCrop 12 | }, 13 | // 生命周期函数--监听页面初次渲染完成 14 | onReady: function () { 15 | this.createContext(); 16 | wx.setKeepScreenOn({ 17 | keepScreenOn: true, 18 | }) 19 | }, 20 | // 生命周期函数--监听页面显示 21 | onShow: function () { 22 | wx.setKeepScreenOn({ 23 | keepScreenOn: true 24 | }) 25 | }, 26 | // 生命周期函数--监听页面卸载 27 | onUnload: function () { 28 | this.stop() 29 | wx.setKeepScreenOn({ 30 | keepScreenOn: false, 31 | }) 32 | }, 33 | onShareAppMessage: function () { 34 | return { 35 | title: '直播播放', 36 | path: 'pages/pull/index', 37 | imageUrl: '/res/img/share.png' 38 | } 39 | }, 40 | createContext: function () { 41 | this.setData({ 42 | livePlayerContext: wx.createLivePlayerContext('livePlayer') 43 | }) 44 | }, 45 | inputSrc: function (e) { 46 | this.checkUrl(e.detail.value) 47 | this.setData({ 48 | src: e.detail.value 49 | }) 50 | this.createContext(); 51 | }, 52 | scanQRCode: function () { 53 | var that = this 54 | that.data.livePlayerContext.stop() 55 | wx.scanCode({ 56 | success: function(res) { 57 | console.log(res) 58 | if (res.scanType === 'QR_CODE' && res.errMsg === 'scanCode:ok') { 59 | that.createContext(); 60 | that.setData({ 61 | src: res.result 62 | }) 63 | } else { 64 | wx.showToast({ 65 | image: '/res/img/err_icon.png', 66 | title: '获取地址失败' 67 | }) 68 | } 69 | } 70 | }) 71 | }, 72 | checkUrl: function (str) { 73 | if (/^(rtmp|RTMP):\/\/[\w\/\.?&_=-]+$/.test(str) || /^(http|HTTP|https|HTTPS):\/\/[\w\/\.&_=-]+\.flv[\w\/\.=?&_-]*$/.test(str)) { 74 | return true 75 | } 76 | wx.showToast({ 77 | image: '/res/img/err_icon.png', 78 | title: '不可用的地址' 79 | }) 80 | return false 81 | }, 82 | play: function () { 83 | if (!this.checkUrl(this.data.src)) { 84 | return 85 | } 86 | var that = this 87 | this.data.livePlayerContext.play({ 88 | success: function (res) { 89 | that.setData({ 90 | pulling: true 91 | }) 92 | }, 93 | fail: function (res) { 94 | wx.showToast({ 95 | image: '/res/img/err_icon.png', 96 | title: '播放失败' 97 | }) 98 | } 99 | }) 100 | that.setData({ 101 | pulling: true 102 | }) 103 | }, 104 | stop: function () { 105 | var that = this 106 | this.data.livePlayerContext.stop({ 107 | success: function (res) { 108 | that.setData({ 109 | pulling: false 110 | }) 111 | }, 112 | fail: function (res) { 113 | wx.showToast({ 114 | image: '/res/img/err_icon.png', 115 | title: '操作失败' 116 | }) 117 | } 118 | }) 119 | }, 120 | pause: function () { 121 | var that = this 122 | this.data.livePlayerContext.pause({ 123 | success: function (res) { 124 | that.setData({ 125 | pulling: false 126 | }) 127 | }, 128 | fail: function (res) { 129 | wx.showToast({ 130 | image: '/res/img/err_icon.png', 131 | title: '操作失败' 132 | }) 133 | } 134 | }) 135 | }, 136 | resume: function () { 137 | var that = this 138 | this.data.livePlayerContext.resume({ 139 | success: function (res) { 140 | that.setData({ 141 | pulling: true 142 | }) 143 | }, 144 | fail: function (res) { 145 | wx.showToast({ 146 | image: '/res/img/err_icon.png', 147 | title: '操作失败' 148 | }) 149 | } 150 | }) 151 | }, 152 | switchMuted() { 153 | this.setData({ 154 | muted: !this.data.muted 155 | }) 156 | }, 157 | switchOrientation() { 158 | this.setData({ 159 | orientation: this.data.orientation === 'vertical' ? 'horizontal' : 'vertical' 160 | }) 161 | }, 162 | swicthObjectFit() { 163 | this.setData({ 164 | objectFit: this.data.objectFit === 'contain' ? 'fillCrop' : 'contain' 165 | }) 166 | }, 167 | statechange(e) { 168 | if ([2006, 3005].indexOf(+e.detail.code) !== -1) { 169 | this.stop() 170 | setTimeout(function () { 171 | wx.showToast({ 172 | image: '/res/img/err_icon.png', 173 | title: '主播停止推流' + e.detail.code 174 | }) 175 | }, 800) 176 | } 177 | if ([-2301, 3001, 3002, 3003].indexOf(+e.detail.code) !== -1) { 178 | this.stop() 179 | setTimeout(function () { 180 | wx.showToast({ 181 | image: '/res/img/err_icon.png', 182 | title: '播放失败' + e.detail.code 183 | }) 184 | }, 800) 185 | } 186 | }, 187 | fullscreenchange(e) { 188 | console.log('fullscreenchange: ', e.detail) 189 | }, 190 | netstatus(e) { 191 | console.log('live-player info:', e.detail.info) 192 | }, 193 | error(e) { 194 | console.error('live-player error:', e.detail.errMsg) 195 | wx.showToast({ 196 | image: '/res/img/err_icon.png', 197 | title: '播放失败' 198 | }) 199 | } 200 | }) 201 | -------------------------------------------------------------------------------- /pages/pull/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "直播播放", 3 | "disableScroll": true 4 | } 5 | -------------------------------------------------------------------------------- /pages/pull/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 29 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 静音 48 | 49 | 50 | 51 | {{objectFit === 'contain' ? '适应' : '填充'}} 52 | 53 | 54 | 55 | {{orientation === 'vertical' ? '竖屏' : '横屏'}} 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /pages/pull/index.wxss: -------------------------------------------------------------------------------- 1 | .ipt { 2 | width: 640rpx; 3 | } 4 | .action { 5 | height: 174rpx; 6 | } 7 | .action-play { 8 | position: absolute; 9 | left: 0; 10 | display: inline-block; 11 | width: 294rpx; 12 | } 13 | .action-play-btn { 14 | margin: 20rpx 0 0 44rpx; 15 | width: 134rpx; 16 | } 17 | .action-play-img { 18 | width: 134rpx; 19 | height: 134rpx; 20 | } 21 | .action-param { 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | display: inline-block; 26 | padding: 0; 27 | width: 450rpx; 28 | } 29 | .action-param-item { 30 | display: inline-block; 31 | margin: 35rpx 40rpx 0 0; 32 | padding: 0; 33 | width: 112rpx; 34 | height: 110rpx; 35 | background: rgba(0, 0, 0, 0.3); 36 | border-radius: 8rpx; 37 | } 38 | .action-param-img { 39 | display: block; 40 | margin: 20rpx auto 12rpx; 41 | width: 44rpx; 42 | height: 44rpx; 43 | } -------------------------------------------------------------------------------- /pages/pullQRCode/index.js: -------------------------------------------------------------------------------- 1 | import drawQrcode from '../../utils/weapp.qrcode.esm.js' 2 | 3 | Page({ 4 | data: { 5 | hlsUrl: '', 6 | rtmpUrl: '', 7 | }, 8 | onLoad: function (option) { 9 | this.getUrl(option) 10 | }, 11 | onShareAppMessage: function () { 12 | return { 13 | title: '直播播放', 14 | path: 'pages/pullQRCode/index?hls=' + encodeURIComponent(this.data.hlsUrl) + '&rtmp=' + encodeURIComponent(this.data.rtmpUrl), 15 | imageUrl: '/res/img/share.png' 16 | } 17 | }, 18 | getUrl: function (option) { 19 | var that = this 20 | if (!option) { 21 | wx.showToast({ 22 | image: '/res/img/err_icon.png', 23 | title: '获取地址失败' 24 | }) 25 | return 26 | } 27 | if (option.rtmp) { 28 | that.setData({ 29 | rtmpUrl: decodeURIComponent(option.rtmp) 30 | }) 31 | drawQrcode({ 32 | width: 160, 33 | height: 160, 34 | canvasId: 'rtmpUrl', 35 | text: this.data.rtmpUrl 36 | }) 37 | } else { 38 | wx.showToast({ 39 | image: '/res/img/err_icon.png', 40 | title: '获取地址失败' 41 | }) 42 | } 43 | if (option.hls) { 44 | that.setData({ 45 | hlsUrl: decodeURIComponent(option.hls) 46 | }) 47 | drawQrcode({ 48 | width: 160, 49 | height: 160, 50 | canvasId: 'hlsUrl', 51 | text: this.data.hlsUrl 52 | }) 53 | } else { 54 | wx.showToast({ 55 | image: '/res/img/err_icon.png', 56 | title: '获取地址失败' 57 | }) 58 | } 59 | }, 60 | }) 61 | -------------------------------------------------------------------------------- /pages/pullQRCode/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "扫码播放", 3 | "disableScroll": true 4 | } -------------------------------------------------------------------------------- /pages/pullQRCode/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | RTMP播放地址 3 | 推荐小程序扫码播放 4 | 5 | HLS播放地址 6 | 推荐微信扫一扫播放 7 | 8 | 9 | -------------------------------------------------------------------------------- /pages/pullQRCode/index.wxss: -------------------------------------------------------------------------------- 1 | .page { 2 | padding-top: 20rpx; 3 | font-size: 24rpx; 4 | color: #333; 5 | text-align: center; 6 | line-height: 40rpx; 7 | } 8 | .tt { 9 | margin-top: 60rpx; 10 | } 11 | .des { 12 | margin-bottom: 20rpx; 13 | } 14 | .qrcode { 15 | margin: 0 auto; 16 | width: 160px; 17 | height: 160px; 18 | } -------------------------------------------------------------------------------- /pages/push/index.js: -------------------------------------------------------------------------------- 1 | const app = getApp() 2 | Page({ 3 | data: { 4 | pushing: false, 5 | isServerAutoUrl: false, // 是否是来自服务器的链接 6 | showModeMenu: false, 7 | rearCamera: false, // 后置摄像头 8 | livePusherContext: {}, 9 | url: '', // 目前仅支持 flv, rtmp 格式 10 | enableCamera: true, // 摄像头开关 11 | beauty: 0, 12 | whiteness: 0, 13 | mode: 'HD', //清晰度 SD(标清), HD(高清), FHD(超清), RTC(实时通话) 14 | orientation: 'vertical', // 竖屏 vertical,horizontal 15 | muted: false, 16 | hlsUrl: '', 17 | rtmpUrl: '' 18 | }, 19 | // 生命周期函数--监听页面初次渲染完成 20 | onReady: function () { 21 | this.getUserLimits() 22 | this.createContext(); 23 | wx.setKeepScreenOn({ 24 | keepScreenOn: true, 25 | }) 26 | }, 27 | // 生命周期函数--监听页面显示 28 | onShow: function () { 29 | var that = this 30 | if (that.data.pushing) { 31 | that.data.livePusherContext.stop() 32 | setTimeout(function () { 33 | that.data.livePusherContext.start() 34 | }, 100) 35 | } 36 | wx.setKeepScreenOn({ 37 | keepScreenOn: true 38 | }) 39 | }, 40 | // 生命周期函数--监听页面卸载 41 | onUnload: function () { 42 | this.stop() 43 | wx.setKeepScreenOn({ 44 | keepScreenOn: false, 45 | }) 46 | }, 47 | onShareAppMessage: function () { 48 | return { 49 | title: '直播推流', 50 | path: 'pages/push/index', 51 | imageUrl: '/res/img/share.png' 52 | } 53 | }, 54 | createContext: function () { 55 | this.setData({ 56 | livePusherContext: wx.createLivePusherContext('videoPush') 57 | }) 58 | }, 59 | // 获取用户授权 60 | getUserLimits: function () { 61 | wx.getSetting({ 62 | success: function (res) { 63 | if (!res.authSetting['scope.camera']) { 64 | wx.authorize({ 65 | scope: 'scope.camera', 66 | success: function () {}, 67 | fail: function () { 68 | wx.showToast({ 69 | image: '/res/img/err_icon.png', 70 | title: '相机授权失败' 71 | }) 72 | } 73 | }) 74 | } 75 | if (!res.authSetting['scope.record']) { 76 | wx.authorize({ 77 | scope: 'scope.record', 78 | success: function () {}, 79 | fail: function () { 80 | wx.showToast({ 81 | image: '/res/img/err_icon.png', 82 | title: '录音授权失败' 83 | }) 84 | } 85 | }) 86 | } 87 | }, 88 | fail: function () { 89 | wx.showToast({ 90 | image: '/res/img/err_icon.png', 91 | title: '授权失败' 92 | }) 93 | } 94 | }) 95 | }, 96 | pullQRCode: function () { 97 | if (!this.data.isServerAutoUrl) { 98 | wx.showToast({ 99 | image: '/res/img/err_icon.png', 100 | title: '不支持扫码' 101 | }) 102 | return 103 | } 104 | var that = this 105 | if (!that.recordJumpToQRCode) { 106 | that.recordJumpToQRCode = setTimeout(function () { 107 | that.recordJumpToQRCode = '' 108 | }, 1000) 109 | wx.navigateTo({ 110 | url: '/pages/pullQRCode/index?hls=' + encodeURIComponent(that.data.hlsUrl) + '&rtmp=' + encodeURIComponent(that.data.rtmpUrl) 111 | }) 112 | } 113 | }, 114 | inputPushUrl: function (e) { 115 | this.checkUrl(e.detail.value) 116 | if (e.detail.value !== this.data.url) { 117 | this.setData({ 118 | url: e.detail.value, 119 | isServerAutoUrl: false 120 | }) 121 | } 122 | }, 123 | getPushUrl: function () { 124 | var that = this 125 | wx.showLoading({ title: '请求中', mask: true }) 126 | wx.request({ 127 | method: 'POST', 128 | url: 'https://app.netease.im/appdemo/weApp/popLive', 129 | success: function (res) { 130 | wx.hideLoading() 131 | if (res.data && res.data.code === 200) { 132 | that.setData({ 133 | url: res.data.data.pushUrl, 134 | hlsUrl: res.data.data.hlsPullUrl, 135 | rtmpUrl: res.data.data.rtmpPullUrl, 136 | isServerAutoUrl: true 137 | }) 138 | wx.showToast({ 139 | title: '获取地址成功' 140 | }) 141 | } else { 142 | wx.showToast({ 143 | image: '/res/img/err_icon.png', 144 | title: '无可用的地址' 145 | }) 146 | } 147 | }, 148 | fail: function () { 149 | wx.hideLoading() 150 | wx.showToast({ 151 | image: '/res/img/err_icon.png', 152 | title: '获取地址失败' 153 | }) 154 | } 155 | }) 156 | }, 157 | // 扫描完成填充地址栏 158 | scanQRCode: function () { 159 | var that = this 160 | that.stop() 161 | wx.scanCode({ 162 | success: function (res) { 163 | console.log(res) 164 | if (res.scanType === 'QR_CODE' && res.errMsg === 'scanCode:ok') { 165 | that.setData({ 166 | url: res.result, 167 | isServerAutoUrl: false 168 | }) 169 | } else { 170 | wx.showToast({ 171 | image: '/res/img/err_icon.png', 172 | title: '获取地址失败' 173 | }) 174 | } 175 | } 176 | }) 177 | }, 178 | checkUrl: function (str) { 179 | if (/^(rtmp|RTMP):\/\/[\w\/\.=?&_=-]+$/.test(str)) { 180 | return true 181 | } 182 | wx.showToast({ 183 | image: '/res/img/err_icon.png', 184 | title: '地址不可用' 185 | }) 186 | return false 187 | }, 188 | // 播放推流 189 | play: function () { 190 | if (!this.checkUrl(this.data.url)) { 191 | return 192 | } 193 | var that = this 194 | this.data.livePusherContext.start({ 195 | success: function (res) { 196 | that.setData({ 197 | pushing: true 198 | }) 199 | }, 200 | fail: function (res) { 201 | wx.showToast({ 202 | image: '/res/img/err_icon.png', 203 | title: '推流失败' 204 | }) 205 | } 206 | }) 207 | }, 208 | // 停止推流 209 | stop: function () { 210 | var that = this 211 | that.setData({ 212 | pushing: false, 213 | url: '', 214 | isServerAutoUrl: false, 215 | enableCamera: true 216 | }) 217 | that.data.livePusherContext.stop() 218 | }, 219 | // 暂停推流 220 | pause: function () { 221 | var that = this 222 | this.data.livePusherContext.pause({ 223 | success: function (res) { 224 | that.setData({ 225 | pushing: false 226 | }) 227 | }, 228 | fail: function (res) { 229 | wx.showToast({ 230 | image: '/res/img/err_icon.png', 231 | title: '操作失败' 232 | }) 233 | } 234 | }) 235 | }, 236 | // 恢复推流 237 | resume: function () { 238 | var that = this 239 | this.data.livePusherContext.resume({ 240 | success: function (res) { 241 | that.setData({ 242 | pushing: true 243 | }) 244 | }, 245 | fail: function (res) { 246 | wx.showToast({ 247 | image: '/res/img/err_icon.png', 248 | title: '操作失败' 249 | }) 250 | } 251 | }) 252 | }, 253 | // 切换摄像头 254 | handoverCamera: function () { 255 | var that = this 256 | this.data.livePusherContext.switchCamera({ 257 | success: function (res) { 258 | that.setData({ 259 | rearCamera: !that.data.rearCamera 260 | }) 261 | }, 262 | fail: function (res) { 263 | wx.showToast({ 264 | image: '/res/img/err_icon.png', 265 | title: '操作失败' 266 | }) 267 | } 268 | }) 269 | }, 270 | switchBeauty: function () { 271 | this.setData({ 272 | beauty: this.data.beauty ? 0 : 10 273 | }) 274 | }, 275 | switchWhiteness: function () { 276 | this.setData({ 277 | whiteness: this.data.whiteness ? 0 : 10 278 | }) 279 | }, 280 | showHideMode: function () { 281 | this.setData({ 282 | showModeMenu: !this.data.showModeMenu 283 | }) 284 | }, 285 | switchMode: function (data) { 286 | this.setData({ 287 | mode: data.target.dataset.type, 288 | showModeMenu: false 289 | }) 290 | }, 291 | switchOrientation: function () { 292 | this.setData({ 293 | orientation: this.data.orientation === 'vertical' ? 'horizontal' : 'vertical' 294 | }) 295 | }, 296 | switchMuted: function () { 297 | this.setData({ 298 | muted: !this.data.muted 299 | }) 300 | }, 301 | switchCamera: function () { 302 | var that = this 303 | that.setData({ 304 | enableCamera: !that.data.enableCamera 305 | }) 306 | if (that.data.pushing) { 307 | that.data.livePusherContext.stop() 308 | setTimeout(function () { 309 | that.data.livePusherContext.start() 310 | }, 500) 311 | } 312 | }, 313 | statechange: function (e) { 314 | if ([-1307, -1308, -1309, -1310, 3001, 3002, 3003, 3004, 3005].indexOf(+e.detail.code) !== -1) { 315 | this.stop() 316 | setTimeout(function () { 317 | wx.showToast({ 318 | image: '/res/img/err_icon.png', 319 | title: '推流失败' + e.detail.code 320 | }) 321 | }, 500) 322 | } 323 | }, 324 | error: function (e) { 325 | console.log('live-pusher errMsg:', e.detail.errMsg, ', errCode:', e.errCode) 326 | this.stop() 327 | setTimeout(function () { 328 | wx.showToast({ 329 | image: '/res/img/err_icon.png', 330 | title: '推流失败' 331 | }) 332 | }, 800) 333 | } 334 | }) 335 | -------------------------------------------------------------------------------- /pages/push/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "直播推流", 3 | "disableScroll": true 4 | } 5 | -------------------------------------------------------------------------------- /pages/push/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 28 | 34 | 35 | 36 | 37 | 38 | 推流 39 | 40 | 41 | 42 | 停止 43 | 44 | 45 | 46 | 47 | 48 | 49 | 美颜 50 | 51 | 52 | 53 | 美白 54 | 55 | 56 | 57 | 58 | 59 | 清晰度 60 | 61 | 62 | 63 | 翻转 64 | 65 | 66 | 67 | 68 | 69 | 静音 70 | 71 | 72 | 73 | 摄像头 74 | 75 | 76 | 77 | {{orientation === 'vertical' ? '竖屏' : '横屏'}} 78 | 79 | 80 | 81 | 扫码播放 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /pages/push/index.wxss: -------------------------------------------------------------------------------- 1 | .ipt-box { 2 | height: 100rpx; 3 | } 4 | .action { 5 | height: 280rpx; 6 | } 7 | .action-play { 8 | position: absolute; 9 | top: 0; 10 | bottom: 0; 11 | left: 0; 12 | width: 220rpx; 13 | } 14 | .action-play-img { 15 | display: inline-block; 16 | margin: 52rpx auto 20rpx; 17 | width: 134rpx; 18 | height: 134rpx; 19 | } 20 | .action-param { 21 | position: absolute; 22 | top: 0; 23 | right: 0; 24 | bottom: 0; 25 | left: 220rpx; 26 | } 27 | .action-param-line { 28 | height: 50%; 29 | } 30 | .action-param-line-1 .action-param-item { 31 | margin-top: 20rpx; 32 | } 33 | .action-param-line-2 .action-param-item { 34 | margin-top: 5rpx; 35 | } 36 | .action-param-item { 37 | position: relative; 38 | display: inline-block; 39 | margin: 0 20rpx 0 0; 40 | width: 112rpx; 41 | height: 110rpx; 42 | background: rgba(0, 0, 0, 0.3); 43 | border-radius: 8rpx; 44 | } 45 | .action-param-img { 46 | display: inline-block; 47 | margin: 18rpx auto 12rpx; 48 | width: 44rpx; 49 | height: 44rpx; 50 | } 51 | .mode-menu { 52 | position: absolute; 53 | bottom: 262rpx; 54 | right: 155rpx; 55 | width: 112rpx; 56 | height: 194rpx; 57 | background: rgba(0,0,0,0.4); 58 | border-radius: 8rpx; 59 | text-align: center; 60 | } 61 | .mode-menu-item { 62 | height: 33.2%; 63 | } 64 | .mode-menu-img { 65 | display: inline-block; 66 | margin-top: 10rpx; 67 | width: 44rpx; 68 | height: 44rpx; 69 | } -------------------------------------------------------------------------------- /pages/video/index.js: -------------------------------------------------------------------------------- 1 | const app = getApp() 2 | Page({ 3 | data: { 4 | playing: false, 5 | fullScreen: false, 6 | videoContext: {}, 7 | src: '', 8 | playSrc: '' 9 | }, 10 | // 生命周期函数--监听页面初次渲染完成 11 | onReady: function () { 12 | wx.setKeepScreenOn({ 13 | keepScreenOn: true, 14 | }) 15 | }, 16 | // 生命周期函数--监听页面显示 17 | onShow: function () { 18 | wx.setKeepScreenOn({ 19 | keepScreenOn: true 20 | }) 21 | }, 22 | // 生命周期函数--监听页面卸载 23 | onUnload: function () { 24 | this.stop() 25 | wx.setKeepScreenOn({ 26 | keepScreenOn: false, 27 | }) 28 | }, 29 | onShareAppMessage: function () { 30 | return { 31 | title: '点播播放', 32 | path: 'pages/video/index', 33 | imageUrl: '/res/img/share.png' 34 | } 35 | }, 36 | createContext: function() { 37 | this.setData({ 38 | videoContext: wx.createVideoContext('video') 39 | }) 40 | }, 41 | inputSrc: function (e) { 42 | if (this.checkUrl(e.detail.value)) { 43 | this.setData({ 44 | playSrc: e.detail.value 45 | }) 46 | this.createContext(); 47 | } else { 48 | this.setData({ 49 | playSrc: '' 50 | }) 51 | } 52 | this.setData({ 53 | src: e.detail.value 54 | }) 55 | }, 56 | tip: function () { 57 | if (this.data.src === '') { 58 | wx.showToast({ 59 | icon: 'none', 60 | title: '请获取地址' 61 | }) 62 | } else { 63 | wx.showToast({ 64 | icon: 'none', 65 | title: '请确认地址' 66 | }) 67 | } 68 | }, 69 | getSrc: function () { 70 | var that = this 71 | that.stop() 72 | wx.showLoading({ title: '请求中', mask: true }) 73 | wx.request({ 74 | method: 'POST', 75 | url: 'https://app.netease.im/appdemo/weApp/vodUrl', 76 | success: function (res) { 77 | wx.hideLoading() 78 | if (res.data && res.data.code === 200) { 79 | that.createContext(); 80 | that.setData({ 81 | playSrc: res.data.data, 82 | src: res.data.data 83 | }) 84 | wx.showToast({ 85 | title: '获取地址成功' 86 | }) 87 | } else { 88 | wx.showToast({ 89 | image: '/res/img/err_icon.png', 90 | title: '获取地址失败' 91 | }) 92 | } 93 | }, 94 | fail: function () { 95 | wx.hideLoading() 96 | wx.showToast({ 97 | image: '/res/img/err_icon.png', 98 | title: '获取地址出错' 99 | }) 100 | } 101 | }) 102 | }, 103 | scanQRCode: function () { 104 | var that = this 105 | this.stop() 106 | wx.scanCode({ 107 | success: function (res) { 108 | if (res.scanType === 'QR_CODE' && res.errMsg === 'scanCode:ok') { 109 | that.createContext(); 110 | that.setData({ 111 | src: res.result 112 | }) 113 | if (that.checkUrl(res.result)) { 114 | that.setData({ 115 | playSrc: res.result 116 | }) 117 | } else { 118 | that.setData({ 119 | playSrc: '' 120 | }) 121 | } 122 | } else { 123 | wx.showToast({ 124 | image: '/res/img/err_icon.png', 125 | title: '获取地址失败' 126 | }) 127 | } 128 | } 129 | }) 130 | }, 131 | checkUrl: function (str) { 132 | if (/^(http|HTTP|https|HTTPS):\/\/[\w\/\.=?&_=-]+(\.mp4)[\w\/\.=?&_=-]*$/.test(str)) { 133 | return true 134 | } 135 | wx.showToast({ 136 | image: '/res/img/err_icon.png', 137 | title: '不支持的地址' 138 | }) 139 | return false 140 | }, 141 | play: function () { 142 | if (this.checkUrl(this.data.playSrc)) { 143 | this.setData({ 144 | playing: true 145 | }) 146 | } else { 147 | this.data.videoContext.stop() 148 | } 149 | }, 150 | pause: function () { 151 | this.setData({ 152 | playing: false 153 | }) 154 | }, 155 | ended: function () { 156 | this.setData({ 157 | playing: false 158 | }) 159 | var that = this 160 | setTimeout(function () { 161 | that.data.videoContext.exitFullScreen() 162 | }, 500) 163 | }, 164 | stop: function() { 165 | if (this.data.videoContext && this.data.videoContext.stop) { 166 | this.setData({ 167 | playing: false, 168 | fullScreen: 'vertical', 169 | }) 170 | this.data.videoContext.stop(); 171 | } 172 | }, 173 | fullscreenchange: function (e) { 174 | this.setData({ 175 | fullScreen: e.detail.fullScreen 176 | }) 177 | wx.showToast({ 178 | icon: 'none', 179 | title: this.data.fullScreen ? '全屏' : '退出全屏', 180 | }) 181 | }, 182 | error: function (e) { 183 | console.error('error: ', e) 184 | this.stop() 185 | wx.showToast({ 186 | image: '/res/img/err_icon.png', 187 | title: '播放失败' 188 | }) 189 | }, 190 | }) 191 | -------------------------------------------------------------------------------- /pages/video/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "点播播放", 3 | "disableScroll": true 4 | } -------------------------------------------------------------------------------- /pages/video/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 35 | 36 | -------------------------------------------------------------------------------- /pages/video/index.wxss: -------------------------------------------------------------------------------- 1 | .page-box { 2 | background: #000; 3 | } 4 | .bgImg { 5 | width: 100%; 6 | height: 100%; 7 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "setting": { 4 | "urlCheck": false, 5 | "es6": true, 6 | "postcss": true, 7 | "minified": true, 8 | "newFeature": true 9 | }, 10 | "compileType": "miniprogram", 11 | "libVersion": "1.9.91", 12 | "appid": "", 13 | "projectname": "weapp-netcall", 14 | "condition": { 15 | "search": { 16 | "current": -1, 17 | "list": [] 18 | }, 19 | "conversation": { 20 | "current": -1, 21 | "list": [] 22 | }, 23 | "miniprogram": { 24 | "current": -1, 25 | "list": [] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /res/img/adapt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/adapt.png -------------------------------------------------------------------------------- /res/img/anyway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/anyway.png -------------------------------------------------------------------------------- /res/img/beauty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/beauty.png -------------------------------------------------------------------------------- /res/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/bg.png -------------------------------------------------------------------------------- /res/img/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/camera.png -------------------------------------------------------------------------------- /res/img/df_hd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/df_hd.png -------------------------------------------------------------------------------- /res/img/df_sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/df_sd.png -------------------------------------------------------------------------------- /res/img/df_ssd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/df_ssd.png -------------------------------------------------------------------------------- /res/img/err_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/err_icon.png -------------------------------------------------------------------------------- /res/img/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/file.png -------------------------------------------------------------------------------- /res/img/future_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/future_icon.png -------------------------------------------------------------------------------- /res/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/logo.png -------------------------------------------------------------------------------- /res/img/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/mute.png -------------------------------------------------------------------------------- /res/img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/pause.png -------------------------------------------------------------------------------- /res/img/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/play.png -------------------------------------------------------------------------------- /res/img/pull_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/pull_icon.png -------------------------------------------------------------------------------- /res/img/push_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/push_icon.png -------------------------------------------------------------------------------- /res/img/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/qrcode.png -------------------------------------------------------------------------------- /res/img/scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/scan.png -------------------------------------------------------------------------------- /res/img/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/share.png -------------------------------------------------------------------------------- /res/img/turnout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/turnout.png -------------------------------------------------------------------------------- /res/img/vod_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/vod_icon.png -------------------------------------------------------------------------------- /res/img/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netease-im/weapp-netcall/581fd537b6af54b653ed5cb7bb211e0d8a699aab/res/img/white.png -------------------------------------------------------------------------------- /utils/weapp.qrcode.esm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * weapp.qrcode.js v0.6.0 (https://github.com/yingye/weapp-qrcode#readme) 3 | */ 4 | 5 | function QR8bitByte(t) { this.mode = QRMode.MODE_8BIT_BYTE, this.data = t } function QRCode(t, e) { this.typeNumber = t, this.errorCorrectLevel = e, this.modules = null, this.moduleCount = 0, this.dataCache = null, this.dataList = new Array } QR8bitByte.prototype = { getLength: function (t) { return this.data.length }, write: function (t) { for (var e = 0; e < this.data.length; e++)t.put(this.data.charCodeAt(e), 8) } }, QRCode.prototype = { addData: function (t) { var e = new QR8bitByte(t); this.dataList.push(e), this.dataCache = null }, isDark: function (t, e) { if (t < 0 || this.moduleCount <= t || e < 0 || this.moduleCount <= e) throw new Error(t + "," + e); return this.modules[t][e] }, getModuleCount: function () { return this.moduleCount }, make: function () { if (this.typeNumber < 1) { var t = 1; for (t = 1; t < 40; t++) { for (var e = QRRSBlock.getRSBlocks(t, this.errorCorrectLevel), r = new QRBitBuffer, o = 0, n = 0; n < e.length; n++)o += e[n].dataCount; for (n = 0; n < this.dataList.length; n++) { var i = this.dataList[n]; r.put(i.mode, 4), r.put(i.getLength(), QRUtil.getLengthInBits(i.mode, t)), i.write(r) } if (r.getLengthInBits() <= 8 * o) break } this.typeNumber = t } this.makeImpl(!1, this.getBestMaskPattern()) }, makeImpl: function (t, e) { this.moduleCount = 4 * this.typeNumber + 17, this.modules = new Array(this.moduleCount); for (var r = 0; r < this.moduleCount; r++) { this.modules[r] = new Array(this.moduleCount); for (var o = 0; o < this.moduleCount; o++)this.modules[r][o] = null } this.setupPositionProbePattern(0, 0), this.setupPositionProbePattern(this.moduleCount - 7, 0), this.setupPositionProbePattern(0, this.moduleCount - 7), this.setupPositionAdjustPattern(), this.setupTimingPattern(), this.setupTypeInfo(t, e), this.typeNumber >= 7 && this.setupTypeNumber(t), null == this.dataCache && (this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList)), this.mapData(this.dataCache, e) }, setupPositionProbePattern: function (t, e) { for (var r = -1; r <= 7; r++)if (!(t + r <= -1 || this.moduleCount <= t + r)) for (var o = -1; o <= 7; o++)e + o <= -1 || this.moduleCount <= e + o || (this.modules[t + r][e + o] = 0 <= r && r <= 6 && (0 == o || 6 == o) || 0 <= o && o <= 6 && (0 == r || 6 == r) || 2 <= r && r <= 4 && 2 <= o && o <= 4) }, getBestMaskPattern: function () { for (var t = 0, e = 0, r = 0; r < 8; r++) { this.makeImpl(!0, r); var o = QRUtil.getLostPoint(this); (0 == r || t > o) && (t = o, e = r) } return e }, createMovieClip: function (t, e, r) { var o = t.createEmptyMovieClip(e, r); this.make(); for (var n = 0; n < this.modules.length; n++)for (var i = 1 * n, a = 0; a < this.modules[n].length; a++) { var s = 1 * a; this.modules[n][a] && (o.beginFill(0, 100), o.moveTo(s, i), o.lineTo(s + 1, i), o.lineTo(s + 1, i + 1), o.lineTo(s, i + 1), o.endFill()) } return o }, setupTimingPattern: function () { for (var t = 8; t < this.moduleCount - 8; t++)null == this.modules[t][6] && (this.modules[t][6] = t % 2 == 0); for (var e = 8; e < this.moduleCount - 8; e++)null == this.modules[6][e] && (this.modules[6][e] = e % 2 == 0) }, setupPositionAdjustPattern: function () { for (var t = QRUtil.getPatternPosition(this.typeNumber), e = 0; e < t.length; e++)for (var r = 0; r < t.length; r++) { var o = t[e], n = t[r]; if (null == this.modules[o][n]) for (var i = -2; i <= 2; i++)for (var a = -2; a <= 2; a++)this.modules[o + i][n + a] = -2 == i || 2 == i || -2 == a || 2 == a || 0 == i && 0 == a } }, setupTypeNumber: function (t) { for (var e = QRUtil.getBCHTypeNumber(this.typeNumber), r = 0; r < 18; r++) { var o = !t && 1 == (e >> r & 1); this.modules[Math.floor(r / 3)][r % 3 + this.moduleCount - 8 - 3] = o } for (r = 0; r < 18; r++) { o = !t && 1 == (e >> r & 1); this.modules[r % 3 + this.moduleCount - 8 - 3][Math.floor(r / 3)] = o } }, setupTypeInfo: function (t, e) { for (var r = this.errorCorrectLevel << 3 | e, o = QRUtil.getBCHTypeInfo(r), n = 0; n < 15; n++) { var i = !t && 1 == (o >> n & 1); n < 6 ? this.modules[n][8] = i : n < 8 ? this.modules[n + 1][8] = i : this.modules[this.moduleCount - 15 + n][8] = i } for (n = 0; n < 15; n++) { i = !t && 1 == (o >> n & 1); n < 8 ? this.modules[8][this.moduleCount - n - 1] = i : n < 9 ? this.modules[8][15 - n - 1 + 1] = i : this.modules[8][15 - n - 1] = i } this.modules[this.moduleCount - 8][8] = !t }, mapData: function (t, e) { for (var r = -1, o = this.moduleCount - 1, n = 7, i = 0, a = this.moduleCount - 1; a > 0; a -= 2)for (6 == a && a--; ;) { for (var s = 0; s < 2; s++)if (null == this.modules[o][a - s]) { var u = !1; i < t.length && (u = 1 == (t[i] >>> n & 1)), QRUtil.getMask(e, o, a - s) && (u = !u), this.modules[o][a - s] = u, -1 == --n && (i++ , n = 7) } if ((o += r) < 0 || this.moduleCount <= o) { o -= r, r = -r; break } } } }, QRCode.PAD0 = 236, QRCode.PAD1 = 17, QRCode.createData = function (t, e, r) { for (var o = QRRSBlock.getRSBlocks(t, e), n = new QRBitBuffer, i = 0; i < r.length; i++) { var a = r[i]; n.put(a.mode, 4), n.put(a.getLength(), QRUtil.getLengthInBits(a.mode, t)), a.write(n) } var s = 0; for (i = 0; i < o.length; i++)s += o[i].dataCount; if (n.getLengthInBits() > 8 * s) throw new Error("code length overflow. (" + n.getLengthInBits() + ">" + 8 * s + ")"); for (n.getLengthInBits() + 4 <= 8 * s && n.put(0, 4); n.getLengthInBits() % 8 != 0;)n.putBit(!1); for (; !(n.getLengthInBits() >= 8 * s || (n.put(QRCode.PAD0, 8), n.getLengthInBits() >= 8 * s));)n.put(QRCode.PAD1, 8); return QRCode.createBytes(n, o) }, QRCode.createBytes = function (t, e) { for (var r = 0, o = 0, n = 0, i = new Array(e.length), a = new Array(e.length), s = 0; s < e.length; s++) { var u = e[s].dataCount, h = e[s].totalCount - u; o = Math.max(o, u), n = Math.max(n, h), i[s] = new Array(u); for (var l = 0; l < i[s].length; l++)i[s][l] = 255 & t.buffer[l + r]; r += u; var f = QRUtil.getErrorCorrectPolynomial(h), g = new QRPolynomial(i[s], f.getLength() - 1).mod(f); a[s] = new Array(f.getLength() - 1); for (l = 0; l < a[s].length; l++) { var R = l + g.getLength() - a[s].length; a[s][l] = R >= 0 ? g.get(R) : 0 } } var d = 0; for (l = 0; l < e.length; l++)d += e[l].totalCount; var c = new Array(d), Q = 0; for (l = 0; l < o; l++)for (s = 0; s < e.length; s++)l < i[s].length && (c[Q++] = i[s][l]); for (l = 0; l < n; l++)for (s = 0; s < e.length; s++)l < a[s].length && (c[Q++] = a[s][l]); return c }; for (var QRMode = { MODE_NUMBER: 1, MODE_ALPHA_NUM: 2, MODE_8BIT_BYTE: 4, MODE_KANJI: 8 }, QRErrorCorrectLevel = { L: 1, M: 0, Q: 3, H: 2 }, QRMaskPattern = { PATTERN000: 0, PATTERN001: 1, PATTERN010: 2, PATTERN011: 3, PATTERN100: 4, PATTERN101: 5, PATTERN110: 6, PATTERN111: 7 }, QRUtil = { PATTERN_POSITION_TABLE: [[], [6, 18], [6, 22], [6, 26], [6, 30], [6, 34], [6, 22, 38], [6, 24, 42], [6, 26, 46], [6, 28, 50], [6, 30, 54], [6, 32, 58], [6, 34, 62], [6, 26, 46, 66], [6, 26, 48, 70], [6, 26, 50, 74], [6, 30, 54, 78], [6, 30, 56, 82], [6, 30, 58, 86], [6, 34, 62, 90], [6, 28, 50, 72, 94], [6, 26, 50, 74, 98], [6, 30, 54, 78, 102], [6, 28, 54, 80, 106], [6, 32, 58, 84, 110], [6, 30, 58, 86, 114], [6, 34, 62, 90, 118], [6, 26, 50, 74, 98, 122], [6, 30, 54, 78, 102, 126], [6, 26, 52, 78, 104, 130], [6, 30, 56, 82, 108, 134], [6, 34, 60, 86, 112, 138], [6, 30, 58, 86, 114, 142], [6, 34, 62, 90, 118, 146], [6, 30, 54, 78, 102, 126, 150], [6, 24, 50, 76, 102, 128, 154], [6, 28, 54, 80, 106, 132, 158], [6, 32, 58, 84, 110, 136, 162], [6, 26, 54, 82, 110, 138, 166], [6, 30, 58, 86, 114, 142, 170]], G15: 1335, G18: 7973, G15_MASK: 21522, getBCHTypeInfo: function (t) { for (var e = t << 10; QRUtil.getBCHDigit(e) - QRUtil.getBCHDigit(QRUtil.G15) >= 0;)e ^= QRUtil.G15 << QRUtil.getBCHDigit(e) - QRUtil.getBCHDigit(QRUtil.G15); return (t << 10 | e) ^ QRUtil.G15_MASK }, getBCHTypeNumber: function (t) { for (var e = t << 12; QRUtil.getBCHDigit(e) - QRUtil.getBCHDigit(QRUtil.G18) >= 0;)e ^= QRUtil.G18 << QRUtil.getBCHDigit(e) - QRUtil.getBCHDigit(QRUtil.G18); return t << 12 | e }, getBCHDigit: function (t) { for (var e = 0; 0 != t;)e++ , t >>>= 1; return e }, getPatternPosition: function (t) { return QRUtil.PATTERN_POSITION_TABLE[t - 1] }, getMask: function (t, e, r) { switch (t) { case QRMaskPattern.PATTERN000: return (e + r) % 2 == 0; case QRMaskPattern.PATTERN001: return e % 2 == 0; case QRMaskPattern.PATTERN010: return r % 3 == 0; case QRMaskPattern.PATTERN011: return (e + r) % 3 == 0; case QRMaskPattern.PATTERN100: return (Math.floor(e / 2) + Math.floor(r / 3)) % 2 == 0; case QRMaskPattern.PATTERN101: return e * r % 2 + e * r % 3 == 0; case QRMaskPattern.PATTERN110: return (e * r % 2 + e * r % 3) % 2 == 0; case QRMaskPattern.PATTERN111: return (e * r % 3 + (e + r) % 2) % 2 == 0; default: throw new Error("bad maskPattern:" + t) } }, getErrorCorrectPolynomial: function (t) { for (var e = new QRPolynomial([1], 0), r = 0; r < t; r++)e = e.multiply(new QRPolynomial([1, QRMath.gexp(r)], 0)); return e }, getLengthInBits: function (t, e) { if (1 <= e && e < 10) switch (t) { case QRMode.MODE_NUMBER: return 10; case QRMode.MODE_ALPHA_NUM: return 9; case QRMode.MODE_8BIT_BYTE: case QRMode.MODE_KANJI: return 8; default: throw new Error("mode:" + t) } else if (e < 27) switch (t) { case QRMode.MODE_NUMBER: return 12; case QRMode.MODE_ALPHA_NUM: return 11; case QRMode.MODE_8BIT_BYTE: return 16; case QRMode.MODE_KANJI: return 10; default: throw new Error("mode:" + t) } else { if (!(e < 41)) throw new Error("type:" + e); switch (t) { case QRMode.MODE_NUMBER: return 14; case QRMode.MODE_ALPHA_NUM: return 13; case QRMode.MODE_8BIT_BYTE: return 16; case QRMode.MODE_KANJI: return 12; default: throw new Error("mode:" + t) } } }, getLostPoint: function (t) { for (var e = t.getModuleCount(), r = 0, o = 0; o < e; o++)for (var n = 0; n < e; n++) { for (var i = 0, a = t.isDark(o, n), s = -1; s <= 1; s++)if (!(o + s < 0 || e <= o + s)) for (var u = -1; u <= 1; u++)n + u < 0 || e <= n + u || 0 == s && 0 == u || a == t.isDark(o + s, n + u) && i++; i > 5 && (r += 3 + i - 5) } for (o = 0; o < e - 1; o++)for (n = 0; n < e - 1; n++) { var h = 0; t.isDark(o, n) && h++ , t.isDark(o + 1, n) && h++ , t.isDark(o, n + 1) && h++ , t.isDark(o + 1, n + 1) && h++ , 0 != h && 4 != h || (r += 3) } for (o = 0; o < e; o++)for (n = 0; n < e - 6; n++)t.isDark(o, n) && !t.isDark(o, n + 1) && t.isDark(o, n + 2) && t.isDark(o, n + 3) && t.isDark(o, n + 4) && !t.isDark(o, n + 5) && t.isDark(o, n + 6) && (r += 40); for (n = 0; n < e; n++)for (o = 0; o < e - 6; o++)t.isDark(o, n) && !t.isDark(o + 1, n) && t.isDark(o + 2, n) && t.isDark(o + 3, n) && t.isDark(o + 4, n) && !t.isDark(o + 5, n) && t.isDark(o + 6, n) && (r += 40); var l = 0; for (n = 0; n < e; n++)for (o = 0; o < e; o++)t.isDark(o, n) && l++; return r += 10 * (Math.abs(100 * l / e / e - 50) / 5) } }, QRMath = { glog: function (t) { if (t < 1) throw new Error("glog(" + t + ")"); return QRMath.LOG_TABLE[t] }, gexp: function (t) { for (; t < 0;)t += 255; for (; t >= 256;)t -= 255; return QRMath.EXP_TABLE[t] }, EXP_TABLE: new Array(256), LOG_TABLE: new Array(256) }, i = 0; i < 8; i++)QRMath.EXP_TABLE[i] = 1 << i; for (i = 8; i < 256; i++)QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8]; for (i = 0; i < 255; i++)QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i; function QRPolynomial(t, e) { if (void 0 == t.length) throw new Error(t.length + "/" + e); for (var r = 0; r < t.length && 0 == t[r];)r++; this.num = new Array(t.length - r + e); for (var o = 0; o < t.length - r; o++)this.num[o] = t[o + r] } function QRRSBlock(t, e) { this.totalCount = t, this.dataCount = e } function QRBitBuffer() { this.buffer = new Array, this.length = 0 } function drawQrcode(t) { t = t || {}, (t = Object.assign({ width: 256, height: 256, typeNumber: -1, correctLevel: QRErrorCorrectLevel.H, background: "#ffffff", foreground: "#000000" }, t)).canvasId ? function () { var e = new QRCode(t.typeNumber, t.correctLevel); e.addData(t.text), e.make(); for (var r = wx.createCanvasContext && wx.createCanvasContext(t.canvasId), o = t.width / e.getModuleCount(), n = t.height / e.getModuleCount(), i = 0; i < e.getModuleCount(); i++)for (var a = 0; a < e.getModuleCount(); a++) { var s = e.isDark(i, a) ? t.foreground : t.background; r.setFillStyle(s); var u = Math.ceil((a + 1) * o) - Math.floor(a * o), h = Math.ceil((i + 1) * o) - Math.floor(i * o); r.fillRect(Math.round(a * o), Math.round(i * n), u, h) } r.draw() }() : console.warn("please you set canvasId!") } QRPolynomial.prototype = { get: function (t) { return this.num[t] }, getLength: function () { return this.num.length }, multiply: function (t) { for (var e = new Array(this.getLength() + t.getLength() - 1), r = 0; r < this.getLength(); r++)for (var o = 0; o < t.getLength(); o++)e[r + o] ^= QRMath.gexp(QRMath.glog(this.get(r)) + QRMath.glog(t.get(o))); return new QRPolynomial(e, 0) }, mod: function (t) { if (this.getLength() - t.getLength() < 0) return this; for (var e = QRMath.glog(this.get(0)) - QRMath.glog(t.get(0)), r = new Array(this.getLength()), o = 0; o < this.getLength(); o++)r[o] = this.get(o); for (o = 0; o < t.getLength(); o++)r[o] ^= QRMath.gexp(QRMath.glog(t.get(o)) + e); return new QRPolynomial(r, 0).mod(t) } }, QRRSBlock.RS_BLOCK_TABLE = [[1, 26, 19], [1, 26, 16], [1, 26, 13], [1, 26, 9], [1, 44, 34], [1, 44, 28], [1, 44, 22], [1, 44, 16], [1, 70, 55], [1, 70, 44], [2, 35, 17], [2, 35, 13], [1, 100, 80], [2, 50, 32], [2, 50, 24], [4, 25, 9], [1, 134, 108], [2, 67, 43], [2, 33, 15, 2, 34, 16], [2, 33, 11, 2, 34, 12], [2, 86, 68], [4, 43, 27], [4, 43, 19], [4, 43, 15], [2, 98, 78], [4, 49, 31], [2, 32, 14, 4, 33, 15], [4, 39, 13, 1, 40, 14], [2, 121, 97], [2, 60, 38, 2, 61, 39], [4, 40, 18, 2, 41, 19], [4, 40, 14, 2, 41, 15], [2, 146, 116], [3, 58, 36, 2, 59, 37], [4, 36, 16, 4, 37, 17], [4, 36, 12, 4, 37, 13], [2, 86, 68, 2, 87, 69], [4, 69, 43, 1, 70, 44], [6, 43, 19, 2, 44, 20], [6, 43, 15, 2, 44, 16], [4, 101, 81], [1, 80, 50, 4, 81, 51], [4, 50, 22, 4, 51, 23], [3, 36, 12, 8, 37, 13], [2, 116, 92, 2, 117, 93], [6, 58, 36, 2, 59, 37], [4, 46, 20, 6, 47, 21], [7, 42, 14, 4, 43, 15], [4, 133, 107], [8, 59, 37, 1, 60, 38], [8, 44, 20, 4, 45, 21], [12, 33, 11, 4, 34, 12], [3, 145, 115, 1, 146, 116], [4, 64, 40, 5, 65, 41], [11, 36, 16, 5, 37, 17], [11, 36, 12, 5, 37, 13], [5, 109, 87, 1, 110, 88], [5, 65, 41, 5, 66, 42], [5, 54, 24, 7, 55, 25], [11, 36, 12], [5, 122, 98, 1, 123, 99], [7, 73, 45, 3, 74, 46], [15, 43, 19, 2, 44, 20], [3, 45, 15, 13, 46, 16], [1, 135, 107, 5, 136, 108], [10, 74, 46, 1, 75, 47], [1, 50, 22, 15, 51, 23], [2, 42, 14, 17, 43, 15], [5, 150, 120, 1, 151, 121], [9, 69, 43, 4, 70, 44], [17, 50, 22, 1, 51, 23], [2, 42, 14, 19, 43, 15], [3, 141, 113, 4, 142, 114], [3, 70, 44, 11, 71, 45], [17, 47, 21, 4, 48, 22], [9, 39, 13, 16, 40, 14], [3, 135, 107, 5, 136, 108], [3, 67, 41, 13, 68, 42], [15, 54, 24, 5, 55, 25], [15, 43, 15, 10, 44, 16], [4, 144, 116, 4, 145, 117], [17, 68, 42], [17, 50, 22, 6, 51, 23], [19, 46, 16, 6, 47, 17], [2, 139, 111, 7, 140, 112], [17, 74, 46], [7, 54, 24, 16, 55, 25], [34, 37, 13], [4, 151, 121, 5, 152, 122], [4, 75, 47, 14, 76, 48], [11, 54, 24, 14, 55, 25], [16, 45, 15, 14, 46, 16], [6, 147, 117, 4, 148, 118], [6, 73, 45, 14, 74, 46], [11, 54, 24, 16, 55, 25], [30, 46, 16, 2, 47, 17], [8, 132, 106, 4, 133, 107], [8, 75, 47, 13, 76, 48], [7, 54, 24, 22, 55, 25], [22, 45, 15, 13, 46, 16], [10, 142, 114, 2, 143, 115], [19, 74, 46, 4, 75, 47], [28, 50, 22, 6, 51, 23], [33, 46, 16, 4, 47, 17], [8, 152, 122, 4, 153, 123], [22, 73, 45, 3, 74, 46], [8, 53, 23, 26, 54, 24], [12, 45, 15, 28, 46, 16], [3, 147, 117, 10, 148, 118], [3, 73, 45, 23, 74, 46], [4, 54, 24, 31, 55, 25], [11, 45, 15, 31, 46, 16], [7, 146, 116, 7, 147, 117], [21, 73, 45, 7, 74, 46], [1, 53, 23, 37, 54, 24], [19, 45, 15, 26, 46, 16], [5, 145, 115, 10, 146, 116], [19, 75, 47, 10, 76, 48], [15, 54, 24, 25, 55, 25], [23, 45, 15, 25, 46, 16], [13, 145, 115, 3, 146, 116], [2, 74, 46, 29, 75, 47], [42, 54, 24, 1, 55, 25], [23, 45, 15, 28, 46, 16], [17, 145, 115], [10, 74, 46, 23, 75, 47], [10, 54, 24, 35, 55, 25], [19, 45, 15, 35, 46, 16], [17, 145, 115, 1, 146, 116], [14, 74, 46, 21, 75, 47], [29, 54, 24, 19, 55, 25], [11, 45, 15, 46, 46, 16], [13, 145, 115, 6, 146, 116], [14, 74, 46, 23, 75, 47], [44, 54, 24, 7, 55, 25], [59, 46, 16, 1, 47, 17], [12, 151, 121, 7, 152, 122], [12, 75, 47, 26, 76, 48], [39, 54, 24, 14, 55, 25], [22, 45, 15, 41, 46, 16], [6, 151, 121, 14, 152, 122], [6, 75, 47, 34, 76, 48], [46, 54, 24, 10, 55, 25], [2, 45, 15, 64, 46, 16], [17, 152, 122, 4, 153, 123], [29, 74, 46, 14, 75, 47], [49, 54, 24, 10, 55, 25], [24, 45, 15, 46, 46, 16], [4, 152, 122, 18, 153, 123], [13, 74, 46, 32, 75, 47], [48, 54, 24, 14, 55, 25], [42, 45, 15, 32, 46, 16], [20, 147, 117, 4, 148, 118], [40, 75, 47, 7, 76, 48], [43, 54, 24, 22, 55, 25], [10, 45, 15, 67, 46, 16], [19, 148, 118, 6, 149, 119], [18, 75, 47, 31, 76, 48], [34, 54, 24, 34, 55, 25], [20, 45, 15, 61, 46, 16]], QRRSBlock.getRSBlocks = function (t, e) { var r = QRRSBlock.getRsBlockTable(t, e); if (void 0 == r) throw new Error("bad rs block @ typeNumber:" + t + "/errorCorrectLevel:" + e); for (var o = r.length / 3, n = new Array, i = 0; i < o; i++)for (var a = r[3 * i + 0], s = r[3 * i + 1], u = r[3 * i + 2], h = 0; h < a; h++)n.push(new QRRSBlock(s, u)); return n }, QRRSBlock.getRsBlockTable = function (t, e) { switch (e) { case QRErrorCorrectLevel.L: return QRRSBlock.RS_BLOCK_TABLE[4 * (t - 1) + 0]; case QRErrorCorrectLevel.M: return QRRSBlock.RS_BLOCK_TABLE[4 * (t - 1) + 1]; case QRErrorCorrectLevel.Q: return QRRSBlock.RS_BLOCK_TABLE[4 * (t - 1) + 2]; case QRErrorCorrectLevel.H: return QRRSBlock.RS_BLOCK_TABLE[4 * (t - 1) + 3]; default: return } }, QRBitBuffer.prototype = { get: function (t) { var e = Math.floor(t / 8); return 1 == (this.buffer[e] >>> 7 - t % 8 & 1) }, put: function (t, e) { for (var r = 0; r < e; r++)this.putBit(1 == (t >>> e - r - 1 & 1)) }, getLengthInBits: function () { return this.length }, putBit: function (t) { var e = Math.floor(this.length / 8); this.buffer.length <= e && this.buffer.push(0), t && (this.buffer[e] |= 128 >>> this.length % 8), this.length++ } }; export default drawQrcode; 6 | --------------------------------------------------------------------------------