@x-edu/live-player 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-edu/live-player",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "main": "dist/XEduLivePlayer.common.js",
5
5
  "scripts": {
6
6
  "start": "cross-env SDP_ENV=local gms dev",
package/src/App.jsx CHANGED
@@ -8,6 +8,7 @@ import { parseAdapter } from '@/util/date'
8
8
 
9
9
  import PublicLiveDetail from './detail'
10
10
  import { getLiveOnlineCount } from './lib/getLiveOnlineCount'
11
+ import PublicLiveList from './list'
11
12
 
12
13
  Icon.url = '/fish/icon/umd-4.1.2.js'
13
14
 
@@ -18,5 +19,6 @@ dayjs.locale('zh-cn')
18
19
 
19
20
  export default {
20
21
  PublicLiveDetail,
21
- getLiveOnlineCount
22
+ getLiveOnlineCount,
23
+ PublicLiveList
22
24
  }
@@ -1,8 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
  import classNames from 'classnames'
3
- import { importImg } from '@/util/ImportImg'
4
-
5
- const defaultAvatar = importImg('@/asset/img/def-avatar.jpg')
3
+ import defaultAvatar from '@/asset/img/def-avatar.jpg'
6
4
 
7
5
  function Avatar({
8
6
  onError = () => {},
@@ -0,0 +1,16 @@
1
+ import React from 'react'
2
+ import classNames from 'classnames'
3
+ import style from './index.module.less'
4
+
5
+ export default function Icon({
6
+ className,
7
+ type
8
+ }) {
9
+ return (
10
+ <svg
11
+ className={classNames(style.icon, className)}
12
+ >
13
+ <use xlinkHref={`#${type}`} />
14
+ </svg>
15
+ )
16
+ }
@@ -0,0 +1,3 @@
1
+ .icon {
2
+ fill: currentColor;
3
+ }
@@ -0,0 +1,27 @@
1
+ import React, { useState } from 'react'
2
+ import Pagination from './index'
3
+
4
+ export default function LocalPagination({
5
+ data,
6
+ pageSize,
7
+ defaultPage = 1,
8
+ renderData,
9
+ ...restProps
10
+ }) {
11
+ const [currentPage, setCurrentPage] = useState(defaultPage)
12
+ const total = data.length
13
+ const pageData = data.slice((currentPage - 1) * pageSize, currentPage * pageSize)
14
+
15
+ return (
16
+ <>
17
+ {pageData.map((dataItem, index) => renderData(dataItem, index))}
18
+ <Pagination
19
+ total={total}
20
+ current={currentPage}
21
+ pageSize={pageSize}
22
+ onChange={setCurrentPage}
23
+ {...restProps}
24
+ />
25
+ </>
26
+ )
27
+ }
@@ -0,0 +1,32 @@
1
+ import React, { useState } from 'react'
2
+ import Pagination from './index'
3
+
4
+ export default function RemotePagination({
5
+ total = 0,
6
+ data,
7
+ pageSize,
8
+ defaultPage = 1,
9
+ renderData,
10
+ onChange = () => {},
11
+ ...restProps
12
+ }) {
13
+ const [currentPage, setCurrentPage] = useState(defaultPage)
14
+
15
+ const handlePageChange = (options) => {
16
+ setCurrentPage(options)
17
+ onChange(options)
18
+ }
19
+
20
+ return (
21
+ <>
22
+ {data.map((dataItem, index) => renderData(dataItem, index))}
23
+ <Pagination
24
+ total={total || data.length || 0}
25
+ current={currentPage}
26
+ pageSize={pageSize}
27
+ onChange={handlePageChange}
28
+ {...restProps}
29
+ />
30
+ </>
31
+ )
32
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react'
2
+ import classNames from 'classnames'
3
+ import { Pagination as FishPagination } from 'fish'
4
+
5
+ import style from './index.module.less'
6
+
7
+ export default function Pagination(props) {
8
+ const { className, current, total, pageSize, onChange = () => { }, showSizeChanger = false } = props
9
+ const pageCount = Math.ceil(total / pageSize)
10
+
11
+ if (pageCount > 1) {
12
+ return (
13
+ <div className={classNames(style.pagination, className, 'custom-pagination')}>
14
+ <span className={style['pagination-text']}>
15
+ {`当前第 ${current} 页/共 ${pageCount} 页,共有 ${total} 条记录`}
16
+ </span>
17
+ <FishPagination
18
+ total={total}
19
+ current={current}
20
+ pageSize={pageSize}
21
+ onChange={onChange}
22
+ showSizeChanger={showSizeChanger}
23
+ showQuickJumper={{
24
+ goButton: false
25
+ }}
26
+ />
27
+ </div>
28
+ )
29
+ }
30
+ return null
31
+ }
@@ -0,0 +1,110 @@
1
+ @primary-color: #1e62ec;
2
+
3
+ .pagination {
4
+ padding-bottom: 24px;
5
+ margin-top: 64px;
6
+ display: flex;
7
+ justify-content: center;
8
+ align-items: center;
9
+ width: 100%;
10
+
11
+ .pagination-text {
12
+ color: #333;
13
+ margin-right: 16px;
14
+ }
15
+
16
+ :global(.fish-pagination-prev), :global(.fish-pagination-next) {
17
+ // display: none;
18
+ button {
19
+ border: 0px;
20
+ }
21
+ }
22
+
23
+ :global(.fish-pagination-item) {
24
+ width: 32px;
25
+ height: 32px;
26
+ line-height: 32px;
27
+ background: #F5F7FA;
28
+ // border: 1px solid @primary-color;
29
+ border: 0px;
30
+ border-radius: 2px;
31
+ font-size: 14px;
32
+ font-weight: 400;
33
+ color: @primary-color;
34
+ margin-right: 10px;
35
+
36
+ a {
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ padding: 0;
41
+ }
42
+ }
43
+
44
+ :global(.fish-pagination-item:hover) {
45
+ background: #F5F7FA;
46
+ }
47
+
48
+ :global(.fish-pagination-item-active:hover) {
49
+ background: @primary-color;
50
+ }
51
+
52
+ :global(.fish-pagination-item-link) {
53
+ background: #F5F7FA;
54
+ }
55
+
56
+ :global(.fish-pagination-item-link:hover) {
57
+ background: #F5F7FA;
58
+ }
59
+
60
+ :global(.fish-pagination-item-active) {
61
+ border-color: @primary-color;
62
+ background: @primary-color;
63
+ a {
64
+ color: #fff;
65
+ }
66
+ }
67
+
68
+ :global(.fish-pagination-options), :global(.fish-pagination-options-quick-jumper) {
69
+ margin-left: 8px;
70
+ }
71
+
72
+ :global(.fish-pagination-options-quick-jumper) {
73
+ :global(input) {
74
+ // border: 0;
75
+ background: transparent;
76
+ border-radius: 0;
77
+ outline: 0;
78
+ // border-bottom: 1px solid @primary-color;
79
+ height: 24px;
80
+ padding: 1px 7px;
81
+ width: 44px;
82
+ text-align: center;
83
+ &:focus {
84
+ outline: none;
85
+ box-shadow: none;
86
+ border-color: @primary-color;
87
+ }
88
+
89
+ &:hover {
90
+ border-color: @primary-color;
91
+ }
92
+ }
93
+
94
+ :global(.fish-pagination-options-quick-jumper-btn) {
95
+ min-width: 40px;
96
+ line-height: 32px;
97
+ height: 32px;
98
+ margin-left: 12px;
99
+ background: transparent;
100
+ border: 0;
101
+ font-weight: 700;
102
+ color: @primary-color;
103
+ &:hover {
104
+ border-color: @primary-color;
105
+ background: @primary-color;
106
+ color: #fff;
107
+ }
108
+ }
109
+ }
110
+ }
@@ -3,7 +3,7 @@ import { generateCommonAPI, generateCommonAPIWithUC } from './helper'
3
3
  const api = generateCommonAPIWithUC({
4
4
  hostKey: 'live-activity',
5
5
  prefix: ''
6
- })
6
+ }, true)
7
7
 
8
8
  export default api
9
9
 
@@ -0,0 +1,11 @@
1
+ import React from 'react'
2
+ import Live from '../App'
3
+
4
+ export default function Detail() {
5
+ return (
6
+ <Live.PublicLiveDetail
7
+ liveId="13aafbce-8ca2-4587-bc6e-eab183c9d5b6"
8
+ appId="e5649925-441d-4a53-b525-51a2f1c4e0a8"
9
+ />
10
+ )
11
+ }
@@ -0,0 +1,62 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import { UC } from '@sdp.nd/nd-uc-sdk'
3
+ import Live from '../App'
4
+
5
+ const uc = new UC({
6
+ env: 'ncet-xedu',
7
+ sdpAppId: 'e5649925-441d-4a53-b525-51a2f1c4e0a8',
8
+ origins: {
9
+ UC: 'https://uc-gateway.ykt.eduyun.cn',
10
+ SSO: 'https://sso.basic.smartedu.cn'
11
+ },
12
+ isGlobalSDKCacheEnabled: true
13
+ })
14
+ // const uc = new UC({
15
+ // env: 'preproduction',
16
+ // sdpAppId: '331593d3-af38-456e-bdb8-5263525608f4',
17
+ // origins: {
18
+ // UC: 'https://uc-gateway.beta.101.com',
19
+ // SSO: 'https://ysc-sso.ykt.eduyun.cn'
20
+ // },
21
+ // isGlobalSDKCacheEnabled: true
22
+ // })
23
+
24
+ export default function List() {
25
+ const [ucInstance, setUcInstance] = useState(uc)
26
+ const [userInfo, setUserInfo] = useState(null)
27
+
28
+ const handleSubscribe = (data) => {
29
+ console.log(data)
30
+ }
31
+
32
+ useEffect(() => {
33
+ async function init() {
34
+ try {
35
+ await ucInstance.login({
36
+ loginName: '18695708674',
37
+ password: 'gyh123456'
38
+ })
39
+ const account = ucInstance.getCurrentAccount()
40
+ const info = await account.getAccountInfo()
41
+ setUserInfo(info)
42
+ } catch (error) {
43
+ console.log(error)
44
+ }
45
+ }
46
+ init()
47
+ }, [])
48
+
49
+ if (!userInfo) {
50
+ return null
51
+ }
52
+
53
+ return (
54
+ <Live.PublicLiveList
55
+ uc={ucInstance}
56
+ loginInfo={{
57
+ userInfo
58
+ }}
59
+ onSubscribe={handleSubscribe}
60
+ />
61
+ )
62
+ }
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import { Tabs } from 'fish'
3
+ import Detail from './Detail'
4
+ import List from './List'
5
+
6
+ export default function Demo() {
7
+ return (
8
+ <Tabs>
9
+ <Tabs.TabPane tab="详情" key="detail">
10
+ <Detail />
11
+ </Tabs.TabPane>
12
+ <Tabs.TabPane tab="列表" key="list">
13
+ <List />
14
+ </Tabs.TabPane>
15
+ </Tabs>
16
+ )
17
+ }
@@ -1,12 +1,13 @@
1
1
  import React, { useState } from 'react'
2
2
  import dayjs from 'dayjs'
3
3
  import { Icon, Spin } from 'fish'
4
- import { PROVIDE_RECORD, PUBLIC_LIVE_STATUS, VIEW_REPLAY, PUBLIC_LIVE_MODE, SUB_TYPE } from '@/config/publicLive'
4
+ import { PROVIDE_RECORD, PUBLIC_LIVE_STATUS, VIEW_REPLAY, PUBLIC_LIVE_MODE } from '@/config/publicLive'
5
5
  import LiveCountDown from '../LiveCountDown'
6
6
  import style from './index.module.less'
7
7
  import AnchorOnTheWay from './AnchorOnTheWay'
8
8
 
9
9
  function LiveStatus({
10
+ isStreamLive,
10
11
  handleLogin,
11
12
  userInfo,
12
13
  liveInfo,
@@ -26,7 +27,6 @@ function LiveStatus({
26
27
  const isPaused = liveInfo.status === PUBLIC_LIVE_STATUS.PASUED
27
28
  const isOffline = liveInfo.status === PUBLIC_LIVE_STATUS.OFFLINE
28
29
  const isRecordLive = liveInfo.type === PUBLIC_LIVE_MODE.RECORDED
29
- const isStreamLive = liveInfo.type === PUBLIC_LIVE_MODE.LIVING && (liveInfo.sub_type === SUB_TYPE.OUTSIDE || liveInfo.sub_type === SUB_TYPE.REBROADCAST)
30
30
 
31
31
  // 当前时间是否在回放时间开始前, 如果没有回放开始时间, 则直接判断未回放还没生成
32
32
  const isBeforeRecordTime = liveInfo.replay_begin_time
@@ -45,6 +45,7 @@ const shouldfixedLoading = window.navigator.userAgent.indexOf('iPad') > -1
45
45
  || isInWX()
46
46
 
47
47
  export default function LiveVideo({
48
+ isStreamLive,
48
49
  handleLogin,
49
50
  userInfo,
50
51
  liveInfo,
@@ -168,6 +169,7 @@ export default function LiveVideo({
168
169
  {showAliPlayer && <AliPlayer options={options} onReady={handleVideoReady} />}
169
170
  {supportM3u8 && (
170
171
  <LiveStatus
172
+ isStreamLive={isStreamLive}
171
173
  handleLogin={handleLogin}
172
174
  userInfo={userInfo}
173
175
  visitTime={visitTime}
@@ -8,6 +8,7 @@ import LiveStatus from '../LiveStatus'
8
8
  import style from './index.module.less'
9
9
 
10
10
  export default function RecordVideo({
11
+ isStreamLive,
11
12
  userInfo,
12
13
  liveInfo,
13
14
  visitTime,
@@ -133,6 +134,7 @@ export default function RecordVideo({
133
134
  onReady={handleVideoReady}
134
135
  />
135
136
  <LiveStatus
137
+ isStreamLive={isStreamLive}
136
138
  userInfo={userInfo}
137
139
  visitTime={visitTime}
138
140
  diffTime={diffTime}
@@ -11,10 +11,9 @@ import {
11
11
  import { getIMLiveInfo, getGuestIMLiveInfo } from '@/service/imBroadcasts'
12
12
  import Loading from '@/component/status/Loading'
13
13
  import {
14
- PUBLIC_LIVE_STATUS, PUBLIC_LIVE_MODE, PUBLIC_LIVE_PUSH_TOPIC, PUBLIC_LIVE_PUSH_EVENT, SUB_TYPE
14
+ PUBLIC_LIVE_STATUS, PUBLIC_LIVE_MODE, SUB_TYPE
15
15
  } from '@/config/publicLive'
16
16
  import { getUrlQuery } from '@/util/url'
17
- import ImPush from '@/util/push'
18
17
  import { getRecordLiveStatus } from '@/util/live'
19
18
  import { isEmpty } from '@/util/object'
20
19
  // import IMChatroom from '@/component/IMChatroom'
@@ -79,7 +78,6 @@ export default function PublicLiveDetail({
79
78
  const isLiveToReplay = !replay
80
79
  const isStreamLive = liveInfo
81
80
  && liveInfo.type === PUBLIC_LIVE_MODE.LIVING
82
- && (liveInfo.sub_type === SUB_TYPE.OUTSIDE || liveInfo.sub_type === SUB_TYPE.REBROADCAST)
83
81
 
84
82
  const handleStatusChange = async (status) => {
85
83
  const newLiveInfo = {
@@ -197,12 +195,6 @@ export default function PublicLiveDetail({
197
195
  }
198
196
  data.imInfo = imLiveInfoResp
199
197
  }
200
- // 推流直播不加载im push
201
- if (data.type === PUBLIC_LIVE_MODE.LIVING && data.sub_type === SUB_TYPE.OUTSIDE) {
202
- // nothing
203
- } else {
204
- await ImPush.init()
205
- }
206
198
  // 如果是转播或者是推流 直接播放(用于调试模式)
207
199
  const { preview } = getUrlQuery() // 后台点预览会加这个参数
208
200
  const generateInfo = preview ? Object.assign(data, {
@@ -213,27 +205,6 @@ export default function PublicLiveDetail({
213
205
  init()
214
206
  }, [])
215
207
 
216
- useEffect(() => {
217
- if (!liveInfo || liveInfo.sub_type === SUB_TYPE.OUTSIDE) {
218
- return
219
- }
220
- const handler = (data) => {
221
- const { extraFields = {} } = data
222
- console.log('public Live:', data)
223
- if (extraFields.event === PUBLIC_LIVE_PUSH_EVENT.STATUS_CHANGE && extraFields.liveId === liveId) {
224
- console.log(`public Live: receive push status ${parseInt(extraFields.status, 10)}`)
225
- handleStatusChange(parseInt(extraFields.status, 10))
226
- }
227
- }
228
- ImPush.addTopicListener(`${PUBLIC_LIVE_PUSH_TOPIC}_${liveId}`, handler)
229
- return () => {
230
- if (!liveInfo || liveInfo.sub_type === SUB_TYPE.OUTSIDE) {
231
- return
232
- }
233
- ImPush.removeTopicListener(`${PUBLIC_LIVE_PUSH_TOPIC}_${liveId}`, handler)
234
- }
235
- }, [!!liveInfo])
236
-
237
208
  useEffect(() => {
238
209
  if (userInfo) {
239
210
  onReportProgress()
@@ -410,6 +381,7 @@ export default function PublicLiveDetail({
410
381
  : isRecordLive
411
382
  ? (
412
383
  <RecordVideo
384
+ isStreamLive={isStreamLive}
413
385
  userInfo={userInfo}
414
386
  liveInfo={liveInfo}
415
387
  visitTime={visitTime}
@@ -426,6 +398,7 @@ export default function PublicLiveDetail({
426
398
  )
427
399
  : (
428
400
  <LiveVideo
401
+ isStreamLive={isStreamLive}
429
402
  handleLogin={handleLogin}
430
403
  userInfo={userInfo}
431
404
  liveInfo={liveInfo}
@@ -470,7 +443,8 @@ export default function PublicLiveDetail({
470
443
  } */}
471
444
  </div>
472
445
  {
473
- !isReplayMode && !isRecordLive && isStreamLive
446
+ !isReplayMode && !isRecordLive
447
+ && liveInfo.sub_type !== SUB_TYPE.NET_DRAGON
474
448
  && liveInfo.status !== PUBLIC_LIVE_STATUS.COMPLETEED
475
449
  && (
476
450
  <LineSwitch
package/src/index.js CHANGED
@@ -1,15 +1,10 @@
1
1
  import React from 'react'
2
2
  import { render } from 'react-dom'
3
3
 
4
- import PublicLive from './App'
5
-
6
- const { PublicLiveDetail } = PublicLive
4
+ import Demo from './demo'
7
5
 
8
6
  // 测试页面内容
9
7
  render(
10
- <PublicLiveDetail
11
- liveId="13aafbce-8ca2-4587-bc6e-eab183c9d5b6"
12
- appId="e5649925-441d-4a53-b525-51a2f1c4e0a8"
13
- />,
8
+ <Demo />,
14
9
  document.querySelector('#root')
15
10
  )
Binary file
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import classNames from 'classnames'
3
+
4
+ import style from './index.module.less'
5
+
6
+ export default function Empty({
7
+ tip = '暂无内容',
8
+ className
9
+ }) {
10
+ return (
11
+ <div className={classNames(style['empty-wrap'], className)}>
12
+ <div className={style['img-wrap']}>
13
+ <div>
14
+ <img src={require('./img/empty.png')} alt="" />
15
+ </div>
16
+ <div className={style.tip}>{tip}</div>
17
+ </div>
18
+ </div>
19
+ )
20
+ }
@@ -0,0 +1,23 @@
1
+ .empty-wrap {
2
+ margin-top: 100px;
3
+
4
+ .img-wrap {
5
+ padding: 16px;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ flex-direction: column;
10
+
11
+ img {
12
+ width: 260px;
13
+ height: 182px;
14
+ }
15
+ }
16
+
17
+ .tip {
18
+ font-size: 16px;
19
+ color: #666;
20
+ line-height: 24px;
21
+ margin-top: 32px;
22
+ }
23
+ }
@@ -0,0 +1,98 @@
1
+ import React, { useState } from 'react'
2
+ import classNames from 'classnames'
3
+ import { message } from 'fish'
4
+ import { find } from 'lodash'
5
+ import { openLiveSubscribe } from '@/service/live'
6
+ import style from './index.module.less'
7
+
8
+ const Actions = {
9
+ 1: '进入直播', // 直播中 或者开播前15min
10
+ 2: '观看回放', // 回放
11
+ 3: '预约', // 预告 且开播时间大于 15min
12
+ 4: '已预约'
13
+ }
14
+
15
+ export default function Action({
16
+ data,
17
+ subscription,
18
+ onActionDetailClick,
19
+ handleLogin,
20
+ isLogin,
21
+ onSubscribe
22
+ }) {
23
+ const { status, live_id: liveId, begin_time: beginTime } = data
24
+ const [curAction, setCurAction] = useState(() => {
25
+ let actionNum
26
+ if (status === 1) {
27
+ actionNum = 1
28
+ } else if (status === 2) {
29
+ actionNum = 2
30
+ } else { // status = 0
31
+ // 如果超过了开播时间,不管有没有预约,都直接进入直播
32
+ const now = new Date().getTime()
33
+ const begin = new Date(beginTime).getTime()
34
+ if (now > begin) {
35
+ actionNum = 1
36
+ return actionNum
37
+ }
38
+ // 开播前15分钟,只提供进入直播按钮
39
+ if (begin > now && (begin - now) < 15 * 60 * 1000) {
40
+ actionNum = 1
41
+ return actionNum
42
+ }
43
+ // 开播前15分钟之外
44
+ const subscriptionItem = find(subscription, {
45
+ live_id: liveId
46
+ })
47
+ if (subscriptionItem?.is_subscribe) { // 已预约
48
+ actionNum = 4
49
+ } else { // 未预约
50
+ actionNum = 3
51
+ }
52
+ }
53
+ return actionNum
54
+ })
55
+
56
+ const handleBtnClick = async (e) => {
57
+ e.stopPropagation()
58
+ if (curAction === 3) {
59
+ try {
60
+ if (!isLogin) {
61
+ handleLogin(data)
62
+ return
63
+ }
64
+ onSubscribe(data)
65
+ await openLiveSubscribe({
66
+ liveId
67
+ })
68
+ message.success('预约成功')
69
+ const now = new Date().getTime()
70
+ const begin = new Date(beginTime).getTime()
71
+ if (begin > now && (begin - now) < 15 * 60 * 1000) {
72
+ setCurAction(1)
73
+ } else {
74
+ setCurAction(4)
75
+ }
76
+ } catch (error) {
77
+ if (error?.response?.data?.message === '直播开始前15分钟无法预约') {
78
+ setCurAction(1)
79
+ }
80
+ message.error(error?.response?.data?.message || '预约失败,请稍后重试')
81
+ }
82
+ } else {
83
+ onActionDetailClick()
84
+ }
85
+ }
86
+
87
+ return (
88
+ <div
89
+ className={classNames(style.action, {
90
+ [style['action-subscribe']]: curAction === 3,
91
+ [style['action-has-subscribe']]: curAction === 4
92
+ })}
93
+ onClick={handleBtnClick}
94
+ >
95
+ {Actions[curAction]}
96
+ </div>
97
+ )
98
+ }