Quellcode durchsuchen

feat: support the calculation of the minimum duration of the program

Casper Dai vor 3 Jahren
Ursprung
Commit
1fa653e06f

+ 2 - 1
src/api/program.js

@@ -37,7 +37,7 @@ export function deleteProgram ({ id, name }) {
 }
 
 export function updateProgram (data) {
-  const { id, itemJsonStr, base64 } = data
+  const { id, duration, itemJsonStr, base64 } = data
   const formData = new FormData()
   const result = /^data:(.+);base64,(.+)$/.exec(base64)
   if (result) {
@@ -50,6 +50,7 @@ export function updateProgram (data) {
     formData.append('file', new Blob([mine], { type: result[1] }))
   }
   formData.append('id', id)
+  formData.append('duration', duration)
   formData.append('itemJsonStr', itemJsonStr)
   return request({
     url: '/item/update',

+ 17 - 12
src/components/Schedule/ScheduleSwiper/index.vue

@@ -152,20 +152,25 @@ export default {
       if (this.editable) {
         return duration
       }
-      if (duration < 60) {
-        return `${duration}秒`
-      }
       const seconds = duration % 60
       let minutes = duration / 60 | 0
-      if (minutes < 60) {
-        return `${minutes}分${seconds}秒`
+      let s = ''
+      if (minutes > 60) {
+        let hours = minutes / 60 | 0
+        s += `${hours / 24 | 0}天`
+        hours %= 24
+        if (hours) {
+          s += `${hours}小时`
+        }
+        minutes %= 60
+      }
+      if (minutes) {
+        s += `${minutes}分`
       }
-      const hours = minutes / 60 | 0
-      minutes = minutes % 60
-      if (hours < 24) {
-        return `${hours}小时${minutes}分${seconds}秒`
+      if (seconds) {
+        s += `${seconds}秒`
       }
-      return `${hours / 24 | 0}天${hours % 24}小时${minutes}分${seconds}秒`
+      return s
     },
     _transformProgram ({ programId, programName, spend }) {
       return {
@@ -181,12 +186,12 @@ export default {
     addProgram () {
       this.choosing = true
     },
-    onChoose ({ id, name }) {
+    onChoose ({ id, name, duration }) {
       this.choosing = false
       this.programs.push({
         key: Math.random().toString(16).slice(2),
         id, name,
-        duration: 10
+        duration: duration || 60
       })
     },
     toView ({ id }) {

+ 10 - 1
src/utils/index.js

@@ -35,7 +35,7 @@ export function parseTime (time, cFormat) {
   const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
     const value = formatObj[key]
     // Note: getDay() returns 0 on Sunday
-    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
     return value.toString().padStart(2, '0')
   })
   return time_str
@@ -69,3 +69,12 @@ export function parseByte (byte) {
   }
   return `${parseInt(byte * 100) / 100}${units[i]}`
 }
+
+export function parseDuration (duration) {
+  if (!duration) {
+    return '未知'
+  }
+  const seconds = `${duration % 60}`
+  const minutes = `${duration / 60 | 0}`
+  return `${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`
+}

+ 10 - 9
src/utils/upload.js

@@ -32,7 +32,7 @@ const CHUNK_SIZE = 10 * 1024 * 1024
 // 开启后,空闲线程优先满足单个文件
 // 关闭时,多线程数为同时上传的文件数最大数,当一个块上传完时会启用空闲线程协助上传
 const COMPLETE_FIRST = false
-const CHECK_VIDEO = false
+const ANALYSIS_FILE = true
 
 const files = []
 const analyzeQueue = []
@@ -60,7 +60,7 @@ export function appendFile (file) {
   console.log(`添加文件${obj.name}, ${obj.totalSize}, ${file.type}`)
   files.push(obj)
   emitChange()
-  if (CHECK_VIDEO && fileType === AssetType.VIDEO) {
+  if (ANALYSIS_FILE && (fileType === AssetType.VIDEO || fileType === AssetType.AUDIO)) {
     analyzeQueue.push(obj)
     analyzeMediaInfo()
   } else {
@@ -90,18 +90,20 @@ function analyzeMediaInfo () {
 
 function analyzeByWorker (obj) {
   return new Promise((resolve, reject) => {
-    console.log(`开始解析视频${obj.name}`)
+    console.log(`开始解析文件${obj.name}`)
     const worker = new Worker('/mediainfo.js')
     worker.onmessage = e => {
       const { error, media } = e.data
       if (error) {
-        console.log(`解析视频${obj.name}失败`, error)
+        console.log(`解析文件${obj.name}失败`, error)
         resolve()
       } else {
         console.log(media)
-        const videoTrack = media.track.find(({ type }) => type === 'VIDEO')
+        const generalTrack = media.track.find(track => track['@type'] === 'General')
+        obj.duration = generalTrack ? parseInt(generalTrack.Duration) | 0 : 0
+
+        const videoTrack = media.track.find(track => track['@type'] === 'Video')
         if (videoTrack) {
-          obj.duration = parseInt(videoTrack.Duration) | 0
           if (videoTrack.Format !== 'AVC') {
             MessageBox.confirm(
               `视频 ${obj.name} 非H264编码将无法预览,确定上传?`,
@@ -597,7 +599,7 @@ function startChunkTask (obj) {
         custom: true,
         background: true
       })
-
+      console.log(`${name}第${index + 1}个切片上传成功`)
       obj.uploaded += 1
       emitChange()
       if (obj.uploaded === totalChunks) {
@@ -607,7 +609,6 @@ function startChunkTask (obj) {
         if (!chunks.length || (sub && !COMPLETE_FIRST && pendingQueue.length)) {
           idleTask()
         } else {
-          console.log(`${name}第${index + 1}个切片上传成功`)
           start(sub)
           // 利用空闲线程
           checkIdle()
@@ -654,7 +655,7 @@ function startMerge (obj) {
       type,
       totalSize,
       totalChunks,
-      duration: type === AssetType.VIDEO ? obj.duration : void 0
+      duration: obj.duration
     },
     timeout: 0,
     custom: true,

+ 6 - 3
src/views/bigscreen/core/base.js

@@ -8,7 +8,8 @@ import {
 import { getAssetUrl } from '@/api/asset'
 import {
   create,
-  inquire,
+  fix,
+  getDuration,
   toJSON
 } from './utils'
 
@@ -74,7 +75,7 @@ export default {
   created () {
     this.fetch()
   },
-  beforeRouteUpdate (from, to, next) {
+  beforeRouteUpdate () {
     window.close()
   },
   beforeRouteLeave () {
@@ -179,6 +180,7 @@ export default {
     },
     initCanvas (canvas) {
       this.node = create(canvas)
+      console.log(this.node)
     },
     checkStatus (status) {
       return true
@@ -217,7 +219,7 @@ export default {
       })
     },
     check () {
-      const warning = inquire(this.node)
+      const warning = fix(this.node)
       if (warning) {
         return this.$confirm(
           `${warning},确定保存?`,
@@ -236,6 +238,7 @@ export default {
         const base64 = await this.snap()
         const result = await updateProgram({
           id: this.program.id,
+          duration: getDuration(this.node),
           itemJsonStr: JSON.stringify(toJSON(this.node)),
           base64
         })

+ 41 - 7
src/views/bigscreen/core/utils.js

@@ -127,27 +127,61 @@ function transform (data, transformOptions = {}, strat = {}) {
   return data
 }
 
-export function inquire (node) {
+export function fix (node) {
   const { bgm, widgets } = node
+
   if (bgm.length === 0) {
     const videoType = widgetVideo.type
     const liveType = widgetLive.type
     const videos = widgets.filter(widget => {
       const type = widget.type
-      if (type === videoType && widget.sources.length) {
+      switch (type) {
+        case videoType:
+        case liveType:
+          return true
+        default:
+          return false
+      }
+    })
+    // 所有带视频组件均有效且无输出时
+    // 仅有一个视频时,默认输出
+    // 多个时,询问
+    if (videos.length && !videos.some(widget => {
+      if (widget.type === videoType && !widget.sources.length) {
         return true
       }
-      if (type === liveType && widget.url) {
+      if (widget.type === liveType && !widget.url) {
         return true
       }
-      return false
-    })
-    if (videos.length > 0 && !videos.some(({ mute }) => mute === 0)) {
-      return '视频均未设置音频输出'
+      return widget.mute === 0
+    })) {
+      if (videos.length === 1) {
+        videos[0].mute = 0
+      } else {
+        return '视频均未设置音频输出'
+      }
     }
   }
 }
 
+export function getDuration (node) {
+  const imageType = widgetImage.type
+  const videoType = widgetVideo.type
+  const liveType = widgetLive.type
+  return node.widgets.reduce((duration, widget) => {
+    switch (widget.type) {
+      case imageType:
+        return widget.sources.length > 1 ? Math.max(widget.sources.length * widget.interval, duration) : duration
+      case videoType:
+        return Math.max(widget.sources.reduce((total, { duration }) => duration ? total + duration : total, 0), duration)
+      case liveType:
+        return Math.max(60, duration)
+      default:
+        return duration
+    }
+  }, 0)
+}
+
 export function validate (node) {
   const { widgets } = node
   if (!widgets.length) {

+ 16 - 12
src/views/bigscreen/designer/index.vue

@@ -13,9 +13,7 @@
       @ended="onAudioEnded"
       @error="onAudioError"
     />
-    <div
-      class="c-designer__side c-side"
-    >
+    <div class="c-designer__side c-side">
       <div class="c-side__tool">
         <div
           class="c-side__item"
@@ -605,12 +603,16 @@ export default {
       }
     },
     saveAssets () {
-      this.changeAttr(this.sources.map(({ name, keyName, size, md5 }) => {
-        return { name, keyName, size, md5 }
+      this.changeAttr(this.sources.map(({ name, keyName, duration, size, md5 }) => {
+        const source = { name, keyName, size, md5 }
+        if (!this.isImage) {
+          source.duration = duration
+        }
+        return source
       }))
       this.handleCloseAssetsDialog()
     },
-    chooseAsset ({ originalName, keyName, url, size, md5 }) {
+    chooseAsset ({ originalName, keyName, url, duration, size, md5 }) {
       const source = {
         name: originalName,
         keyName,
@@ -620,6 +622,8 @@ export default {
       if (this.dialogVisibleAssets) {
         if (this.isImage) {
           source.url = url
+        } else {
+          source.duration = duration
         }
         this.sources.unshift(source)
       } else {
@@ -761,7 +765,7 @@ $drak: #242a30;
     overflow: visible;
 
     &.dragging::after {
-      content: '';
+      content: "";
       position: absolute;
       top: 0;
       left: 0;
@@ -829,13 +833,13 @@ $drak: #242a30;
     }
 
     &::-webkit-scrollbar-track {
-      box-shadow: 1px 1px 5px rgba(116, 148, 170, .5) inset;
+      box-shadow: 1px 1px 5px rgba(116, 148, 170, 0.5) inset;
     }
 
     &::-webkit-scrollbar-thumb {
       min-height: 20px;
       background-clip: content-box;
-      box-shadow: 0 0 0 5px rgba(116, 148, 170, .5) inset;
+      box-shadow: 0 0 0 5px rgba(116, 148, 170, 0.5) inset;
     }
   }
 }
@@ -884,14 +888,14 @@ $drak: #242a30;
 .c-card {
   display: inline-block;
   position: relative;
-  background-color: rgba(0, 0, 0, .8);
+  background-color: rgba(0, 0, 0, 0.8);
 
   &:hover &__icon {
     display: block;
     padding: 6px;
     color: #fff;
     border-radius: 50%;
-    background-color: rgba(0, 0, 0, .6);
+    background-color: rgba(0, 0, 0, 0.6);
     cursor: pointer;
   }
 
@@ -921,7 +925,7 @@ $drak: #242a30;
     color: $gray;
     font-size: 14px;
     text-align: center;
-    background-color: rgba(0, 0, 0, .6);
+    background-color: rgba(0, 0, 0, 0.6);
   }
 
   &__text {

+ 27 - 24
src/views/media/index.vue

@@ -54,25 +54,9 @@
         :curr="currObj"
         @pagination="getList"
       >
-        <el-table-column
-          v-if="isImage"
-          label="缩略图"
-          align="center"
-          width="100"
-          class-name="c-thumbnail-col"
-        >
-          <template v-slot="scope">
-            <i
-              class="o-thumbnail u-pointer"
-              :style="{ 'background-image': `url('${scope.row.thumbnail}')` }"
-              @click="toView(scope.row)"
-            />
-          </template>
-        </el-table-column>
         <el-table-column
           label="文件名"
           align="center"
-          min-width="100"
           show-overflow-tooltip
         >
           <template v-slot="scope">
@@ -87,11 +71,27 @@
             </template>
           </template>
         </el-table-column>
+        <el-table-column
+          :label="isImage ? '缩略图' : '时长'"
+          align="center"
+          width="160"
+          class-name="c-thumbnail-col"
+        >
+          <template v-slot="scope">
+            <i
+              v-if="isImage"
+              class="o-thumbnail u-pointer"
+              :style="{ 'background-image': `url('${scope.row.thumbnail}')` }"
+              @click="toView(scope.row)"
+            />
+            <span v-else>{{ scope.row.duration }}</span>
+          </template>
+        </el-table-column>
         <el-table-column
           prop="size"
           label="文件大小"
           align="center"
-          min-width="100"
+          show-overflow-tooltip
         />
         <el-table-column
           prop="createTime"
@@ -147,7 +147,8 @@ import {
 } from '@/constant'
 import {
   createListOptions,
-  parseByte
+  parseByte,
+  parseDuration
 } from '@/utils'
 import {
   addListener,
@@ -243,6 +244,8 @@ export default {
       asset.name = asset.originalName
       if (asset.type === AssetType.IMAGE) {
         asset.thumbnail = getThumbnailUrl(asset.keyName, '64,fit')
+      } else {
+        asset.duration = parseDuration(asset.duration)
       }
       asset.size = parseByte(asset.size)
       return asset
@@ -345,15 +348,15 @@ export default {
 
         .c-sidebar__icon {
           &.img {
-            background-image: url('~@/assets/icon_image_focus.png');
+            background-image: url("~@/assets/icon_image_focus.png");
           }
 
           &.video {
-            background-image: url('~@/assets/icon_video_focus.png');
+            background-image: url("~@/assets/icon_video_focus.png");
           }
 
           &.audio {
-            background-image: url('~@/assets/icon_audio_focus.png');
+            background-image: url("~@/assets/icon_audio_focus.png");
           }
         }
       }
@@ -366,15 +369,15 @@ export default {
     margin-right: 10px;
 
     &.img {
-      background-image: url('~@/assets/icon_image_normal.png');
+      background-image: url("~@/assets/icon_image_normal.png");
     }
 
     &.video {
-      background-image: url('~@/assets/icon_video_normal.png');
+      background-image: url("~@/assets/icon_video_normal.png");
     }
 
     &.audio {
-      background-image: url('~@/assets/icon_audio_normal.png');
+      background-image: url("~@/assets/icon_audio_normal.png");
     }
   }
 }

+ 33 - 9
src/views/review/components/ReviewAsset.vue

@@ -5,9 +5,21 @@
       @pagination="getList"
     >
       <el-table-column
-        label="缩略图"
+        prop="originalName"
+        label="文件名"
         align="center"
-        width="100"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        prop="typeName"
+        label="类型"
+        align="center"
+        show-overflow-tooltip
+      />
+      <el-table-column
+        label="信息"
+        align="center"
+        width="160"
         class-name="c-thumbnail-col"
       >
         <template v-slot="scope">
@@ -17,14 +29,9 @@
             :style="{ 'background-image': `url('${scope.row.thumbnail}')` }"
             @click="toView(scope.row)"
           />
+          <template v-else>{{ scope.row.duration }}</template>
         </template>
       </el-table-column>
-      <el-table-column
-        prop="originalName"
-        label="文件名"
-        align="center"
-        show-overflow-tooltip
-      />
       <el-table-column
         prop="createBy"
         label="申请人"
@@ -73,6 +80,8 @@ import {
   getAssets,
   getThumbnailUrl
 } from '@/api/asset'
+import { parseDuration } from '@/utils'
+import { AssetType } from '@/constant'
 import mixin from './mixin'
 import Preview from '@/components/Preview'
 
@@ -85,8 +94,23 @@ export default {
   methods: {
     _getList: getAssets,
     transform (asset) {
-      if (asset.type === 1) {
+      if (asset.type === AssetType.IMAGE) {
         asset.thumbnail = getThumbnailUrl(asset.keyName, '64,fit')
+      } else {
+        asset.duration = parseDuration(asset.duration)
+      }
+      switch (asset.type) {
+        case AssetType.IMAGE:
+          asset.typeName = '图片'
+          break
+        case AssetType.VIDEO:
+          asset.typeName = '视频'
+          break
+        case AssetType.AUDIO:
+          asset.typeName = '音频'
+          break
+        default:
+          break
       }
       asset.createBy = asset.userName || asset.createBy
       return asset