Vue 비디오 미디어 다 중 재단 구성 요소 의 구현 예시

18317 단어 Vue재단 하 다.
최근 프로젝트 에 새로운 수요 가 생 겨 서 영상 이나 오디 오 를 여러 단락 재단 한 후에 조합 해 야 한다.예 를 들 어 한 토막 의 동 영상 은 30 분 이 걸 리 고 나 는 5-10 분,17-22 분,24-29 분 이라는 세 단락 을 한 토막 의 동 영상 으로 연결 해 야 한다.재단 은 앞쪽 에 있 고,조립 은 뒤쪽 에 있다.
인터넷 에서 간단하게 찾 아 보 니 기본적으로 클 라 이언 트 안의 도구 이 고 순수한 웹 페이지 의 재단 이 없다.없 으 니 손 을 써 라.
코드 가 GitHub 에 업로드 되 었 습 니 다:https://github.com/fengma1992/media-cut-tool
쓸데없는 말 이 많 지 않 으 니,다음은 어떻게 설계 하 는 지 보 자.
효과 도

그림 의 아래쪽 기능 블록 은 재단 도구 구성 요소 이 고 위의 영상 은 프레젠테이션 용 이 며 물론 오디 오 일 수도 있 습 니 다.
기능 특징:
  • 마우스 드래그 입력 과 키보드 숫자 입력 두 가지 모드 를 지원 합 니 다.
  • 지정 한 재단 세 션 미리 보기 재생 을 지원 합 니 다.
  • 왼쪽 마우스 입력 과 오른쪽 키보드 입력 연결;
  • 마우스 이동 시 하 이 라이트 드래그 바 자동 포착 하기;
  • 재단 시 자동 으로 무 게 를 제거 하 는 것 을 확인 합 니 다.
  • *비고:항목 의 아이콘 이 모두 텍스트 로 바 뀌 었 습 니 다.
    사고의 방향
    전체적으로 보면 하나의 데이터 배열cropItemList을 통 해 사용자 의 입력 데 이 터 를 저장 하고 마우스 드래그 든 키보드 입력 이 든 모두 조작cropItemList을 통 해 양쪽 데이터 연동 을 실현 한다.마지막 으로 사용자 가 원 하 는 재단 을 처리cropItemList를 통 해 출력 합 니 다.cropItemList구 조 는 다음 과 같다.
    
    cropItemList: [
     {
      startTime: 0, //     
      endTime: 100, //     
      startTimeArr: [hoursStr, minutesStr, secondsStr], //       
      endTimeArr: [hoursStr, minutesStr, secondsStr], //       
      startTimeIndicatorOffsetX: 0, //           X   
      endTimeIndicatorOffsetX: 100, //           X   
     }
    ]
    첫걸음
    다 중 재단 인 만큼 사용 자 는 어떤 시간 대 를 재단 하 였 는 지 알 아야 합 니 다.이것 은 오른쪽 재단 목록 을 통 해 보 여 줍 니 다.
    리스트
    목록 에 세 개의 상태 가 존재 합 니 다:
    데이터 없 음 상태

    데이터 가 없 을 때 내용 이 비어 있 습 니 다.사용자 가 입력 상 자 를 클릭 할 때 자발적으로 데 이 터 를 만 듭 니 다.기본 값 은 비디오 길이 의 1/4 에서 3/4 입 니 다.
    데이터 가 하나 있어 요.

    이 때 화면 은 매우 간단 하고 유일한 데 이 터 를 보 여 줍 니 다.
    여러 개의 데이터 가 있다

    여러 개의 데이터 가 있 을 때 추가 로 처리 해 야 합 니 다.첫 번 째 데이터 가 맨 아래 에 있 기 때문에v-for로 순환cropItemList하면 다음 그림 의 상황 이 나타 납 니 다.

    그리고 첫 번 째 오른쪽 은 버튼 을 추가 하고 나머지 오른쪽 은 삭제 버튼 이다.그래서 우 리 는 1 조 를 따로 제시 하여 쓴 다음 에cropItemList역 서 를 하나의renderList생 성하 고 순환renderList하 는0 -> listLength - 2조 를 생 성 한다.
    됐다.
    
    <template v-for="(item, index) in renderList">
     <div v-if="index < listLength -1"
       :key="index"
       class="crop-time-item">
       ...
       ...
     </div>
    </template>
    다음 그림 은 최종 효과:

    시 분 초 입력
    이것 은 바로 세 개의input상 자 를 쓰 고type="text"type=number입력 상자 오른쪽 에 상하 화살표 가 있 을 것 이 라 고 설정 한 다음 에 input 이 벤트 를 감청 하여 입력 의 정확성 을 확보 하고 데 이 터 를 업데이트 하 는 것 이다.focus 사건 을 감청 하여cropItemList이 비어 있 을 때 자발적으로 데 이 터 를 추가 해 야 하 는 지 확인 합 니 다.
    
    <div class="time-input">
     <input type="text"
      :value="renderList[listLength -1]
      && renderList[listLength -1].startTimeArr[0]"
      @input="startTimeChange($event, 0, 0)"
      @focus="inputFocus()"/>
     :
     <input type="text"
      :value="renderList[listLength -1]
      && renderList[listLength -1].startTimeArr[1]"
      @input="startTimeChange($event, 0, 1)"
      @focus="inputFocus()"/>
     :
     <input type="text"
      :value="renderList[listLength -1]
      && renderList[listLength -1].startTimeArr[2]"
      @input="startTimeChange($event, 0, 2)"
      @focus="inputFocus()"/>
    </div>
    세 션 재생
    재생 단 추 를 누 르 면playingItem을 통 해 현재 재생 되 고 있 는 세 션 을 기록 한 다음 상부 에play이 벤트 를 보 내 고 재생 시작 시간 을 가 져 옵 니 다.마찬가지 로pausestop사건 도 있어 언론 의 일시 정지 와 정 지 를 통제 했다.
    
    <CropTool :duration="duration"
       :playing="playing"
       :currentPlayingTime="currentTime"
       @play="playVideo"
       @pause="pauseVideo"
       @stop="stopVideo"/>
    
    /**
     *       
     * @param index
     */
    playSelectedClip: function (index) {
     if (!this.listLength) {
      console.log('     ')
      return
     }
     this.playingItem = this.cropItemList[index]
     this.playingIndex = index
     this.isCropping = false
     
     this.$emit('play', this.playingItem.startTime || 0)
    }
    여기 서 재생 을 시작 하 는 것 을 제어 합 니 다.그러면 어떻게 미디어 를 재단 이 끝 날 때 까지 자동 으로 멈 추 게 합 니까?
    언론 의timeupdate사건 을 감청 하고 실시 간 으로 언론 의currentTimeplayingItemendTime을 비교 하여 도달 할 때pause사건 을 언론 에 알 리 고 중단 했다.
    
    if (currentTime >= playingItem.endTime) {
     this.pause()
    }
    이로써 키보드 입력 의 재단 목록 이 기본적으로 완성 되 었 으 며 마우스 드래그 입력 을 소개 합 니 다.
    두 번 째 단계
    마우스 클릭 과 드래그 를 통 해 입력 하 는 방법 을 소개 한다.
    1.마우스 상호작용 논리 확인
    새로 추 가 된 재단
    마 우 스 를 드래그 구역 에서 클릭 한 후 재단 데 이 터 를 추가 합 니 다.시작 시간 과 종료 시간 이 모두mouseup일 때 진도 바 의 시간 이 고 종료 시간 스탬프 가 마 우 스 를 따라 이동 하여 편집 상태 에 들 어 갑 니 다.
    타임 스탬프 확인
    상 태 를 편집 하고 마우스 가 이동 할 때 시간 스탬프 는 마우스 가 진행 바 의 현재 위치 에 따라 움 직 입 니 다.마 우 스 를 다시 클릭 한 후 현재 시간 을 확인 하고 시간 스탬프 를 종료 하고 마 우 스 를 따라 이동 합 니 다.
    시간 변경
    편집 되 지 않 은 상태 에서 마우스 가 진행 막대 에서 이동 할 때 감청mousemove사건 은 임의의 재단 데이터 의 시작 이나 끝 시간 스탬프 에 가 까 울 때 현재 데 이 터 를 밝 히 고 시간 스탬프 를 표시 합 니 다.마우스mousedown를 누 른 후 시간 표를 선택 하고 시간 변경 데 이 터 를 끌 기 시작 합 니 다.mouseup이후 변경 사항 을 종료 합 니 다.
    2.감청 할 마우스 이벤트 확인
    마 우 스 는 진도 구역 에서 세 가지 사건 을 감청 해 야 한다.mousedown,mousemove,mouseup.진도 구역 에 여러 가지 요소 가 존재 합 니 다.간단하게 세 가지 로 나 눌 수 있 습 니 다.
  • 마우스 이동 시 움 직 이 는 타임 스탬프
  • 세 션 을 자 를 때 시작 시간 스탬프,끝 시간 스탬프,옅 은 파란색 시간 커버
  • 가 존재 합 니 다.
  • 진도 항목 자체
  • 우선mousedownmouseup의 감청 은 당연히 진도 조 자체 에 귀속 된다.
    
    this.timeLineContainer.addEventListener('mousedown', e => {
      const currentCursorOffsetX = e.clientX - containerLeft
      lastMouseDownOffsetX = currentCursorOffsetX
      //           
      this.timeIndicatorCheck(currentCursorOffsetX, 'mousedown')
     })
     
    this.timeLineContainer.addEventListener('mouseup', e => {
    
     //          ,    ,       
     if (this.isCropping) {
      this.stopCropping()
      return
     }
    
     const currentCursorOffsetX = this.getFormattedOffsetX(e.clientX - containerLeft)
     // mousedown mouseup     ,       ,    
     if (Math.abs(currentCursorOffsetX - lastMouseDownOffsetX) > 3) {
      return
     }
    
     //            
     this.currentCursorTime = currentCursorOffsetX * this.timeToPixelRatio
    
     //           
     if (!this.isCropping) {
      this.addNewCropItemInSlider()
    
      //             
      this.startCropping(this.cropItemList.length - 1)
     }
    })
    mousemove이것 은 편집 상태 가 아 닐 때 당연히 감청 진도 조 를 통 해 시간 스탬프 가 마 우 스 를 움 직 이 는 것 을 실현 한다.그리고 시작 이나 끝 시간 스탬프 를 선택 하여 편집 상태 에 들 어가 야 할 때 저 는 처음에 시간 스탬프 자 체 를 감청 하여 선택 한 시간 스탬프 의 목적 을 달성 하려 고 생각 했 습 니 다.실제 상황 은 마우스 가 시작 이나 끝 시간 스탬프 에 가 까 울 때 마우스 가 움 직 이 는 시간 스탬프 가 앞 에 가 려 져 있 고 재단 세 션 이론 적 으로 무한 증가 할 수 있 기 때문에 저 는 2*재단 세 션 개mousemove를 감청 해 야 합 니 다.
    이 를 바탕 으로 진도 항목 자체 감청mousemove에서 만 실시 간 으로 마우스 위치 와 시간 스탬프 위 치 를 비교 하여 해당 위치 에 도 착 했 는 지 확인 하려 면 당연히 하나throttle를 추가 하여 절 류 를 해 야 한다.
    
    this.timeLineContainer.addEventListener('mousemove', e => {
     throttle(() => {
      const currentCursorOffsetX = e.clientX - containerLeft
      // mousemove    
      if (currentCursorOffsetX < 0 || currentCursorOffsetX > containerWidth) {
       this.isCursorIn = false
       //               mouseup  
       if (this.isCropping) {
        this.stopCropping()
        this.timeIndicatorCheck(currentCursorOffsetX < 0 ? 0 : containerWidth, 'mouseup')
       }
       return
      }
      else {
       this.isCursorIn = true
      }
    
      this.currentCursorTime = currentCursorOffsetX * this.timeToPixelRatio
      this.currentCursorOffsetX = currentCursorOffsetX
      //      
      this.timeIndicatorCheck(currentCursorOffsetX, 'mousemove')
      //        
      this.timeIndicatorMove(currentCursorOffsetX)
     }, 10, true)()
    })
    
    3.드래그 와 시간 스탬프 수 동 실현
    다음은 타임 스탬프 검사 와 타임 스탬프 이동 검사 코드 입 니 다.
    
    timeIndicatorCheck (currentCursorOffsetX, mouseEvent) {
     //      ,    
     if (this.isCropping) {
      return
     }
    
     //     ,  hover  
     this.startTimeIndicatorHoverIndex = -1
     this.endTimeIndicatorHoverIndex = -1
     this.startTimeIndicatorDraggingIndex = -1
     this.endTimeIndicatorDraggingIndex = -1
     this.cropItemHoverIndex = -1
    
     this.cropItemList.forEach((item, index) => {
      if (currentCursorOffsetX >= item.startTimeIndicatorOffsetX
       && currentCursorOffsetX <= item.endTimeIndicatorOffsetX) {
       this.cropItemHoverIndex = index
      }
    
      //                     
      if (isCursorClose(item.endTimeIndicatorOffsetX, currentCursorOffsetX)) {
       this.endTimeIndicatorHoverIndex = index
       //     ,    
       if (mouseEvent === 'mousedown') {
        this.endTimeIndicatorDraggingIndex = index
        this.currentEditingIndex = index
        this.isCropping = true
       }
      }
    
      else if (isCursorClose(item.startTimeIndicatorOffsetX, currentCursorOffsetX)) {
       this.startTimeIndicatorHoverIndex = index
       //     ,    
       if (mouseEvent === 'mousedown') {
        this.startTimeIndicatorDraggingIndex = index
        this.currentEditingIndex = index
        this.isCropping = true
       }
      }
     })
    },
    
    timeIndicatorMove (currentCursorOffsetX) {
     //     ,     
     if (this.isCropping) {
      const currentEditingIndex = this.currentEditingIndex
      const startTimeIndicatorDraggingIndex = this.startTimeIndicatorDraggingIndex
      const endTimeIndicatorDraggingIndex = this.endTimeIndicatorDraggingIndex
      const currentCursorTime = this.currentCursorTime
    
      let currentItem = this.cropItemList[currentEditingIndex]
      //         
      if (startTimeIndicatorDraggingIndex > -1 && currentItem) {
       //              
       if (currentCursorOffsetX > currentItem.endTimeIndicatorOffsetX) {
        return
       }
       currentItem.startTimeIndicatorOffsetX = currentCursorOffsetX
       currentItem.startTime = currentCursorTime
      }
    
      //         
      if (endTimeIndicatorDraggingIndex > -1 && currentItem) {
       //              
       if (currentCursorOffsetX < currentItem.startTimeIndicatorOffsetX) {
        return
       }
       currentItem.endTimeIndicatorOffsetX = currentCursorOffsetX
       currentItem.endTime = currentCursorTime
      }
      this.updateCropItem(currentItem, currentEditingIndex)
     }
    }
    세 번 째 단계
    재단 이 끝 난 후 다음 단 계 는 당연히 데 이 터 를 백 엔 드 에 버 리 는 것 이다.
    사용 자 를 sweet감자:(\#고구마\#)
    사용자 가 사용 할 때 작은 손 을 떨 고 단 추 를 한 번 더 누 르 거나 파 킨 슨 이 있어 서 아무리 해도 지연 되 지 않 으 면 데이터 가 같 거나 겹 치 는 부분 이 있 을 수 있 습 니 다.그럼 중복 과 중복 부분 이 있 는 재단 을 걸 러 내야 합 니 다.
    그냥 코드 를 보 는 게 편 해 요.
    
    /**
     * cropItemList     
     */
    cleanCropItemList () {
     let cropItemList = this.cropItemList
     
     // 1.   startTime      
     cropItemList = cropItemList.sort(function (item1, item2) {
      return item1.startTime - item2.startTime
     })
    
     let tempCropItemList = []
     let startTime = cropItemList[0].startTime
     let endTime = cropItemList[0].endTime
     const lastIndex = cropItemList.length - 1
    
     //   ,      
     cropItemList.forEach((item, index) => {
      //        ,    
      if (lastIndex === index) {
       tempCropItemList.push({
        startTime: startTime,
        endTime: endTime,
        startTimeArr: formatTime.getFormatTimeArr(startTime),
        endTimeArr: formatTime.getFormatTimeArr(endTime),
       })
       return
      }
      // currentItem    item
      if (item.endTime <= endTime && item.startTime >= startTime) {
       return
      }
      // currentItem   item   
      if (item.startTime <= endTime && item.endTime >= endTime) {
       endTime = item.endTime
       return
      }
      // currentItem   item   ,       ,      
      if (item.startTime > endTime) {
       tempCropItemList.push({
        startTime: startTime,
        endTime: endTime,
        startTimeArr: formatTime.getFormatTimeArr(startTime),
        endTimeArr: formatTime.getFormatTimeArr(endTime),
       })
       //        item
       startTime = item.startTime
       endTime = item.endTime
      }
     })
    
     return tempCropItemList
    }
    네 번 째 단계
    재단 도구 사용:props 및 emit 이 벤트 를 통 해 미디어 와 재단 도구 간 의 통신 을 실현 합 니 다.
    
    <template>
     <div id="app">
      <video ref="video" src="https://pan.prprpr.me/?/dplayer/hikarunara.mp4"
      controls
      width="600px">
      </video>
      <CropTool :duration="duration"
         :playing="playing"
         :currentPlayingTime="currentTime"
         @play="playVideo"
         @pause="pauseVideo"
         @stop="stopVideo"/>
     </div>
    </template>
    
    <script>
     import CropTool from './components/CropTool.vue'
     
     export default {
      name: 'app',
      components: {
       CropTool,
      },
      data () {
       return {
        duration: 0,
        playing: false,
        currentTime: 0,
       }
      },
      mounted () {
       const videoElement = this.$refs.video
       videoElement.ondurationchange = () => {
        this.duration = videoElement.duration
       }
       videoElement.onplaying = () => {
        this.playing = true
       }
       videoElement.onpause = () => {
        this.playing = false
       }
       videoElement.ontimeupdate = () => {
        this.currentTime = videoElement.currentTime
       }
      },
      methods: {
       seekVideo (seekTime) {
        this.$refs.video.currentTime = seekTime
       },
       playVideo (time) {
        this.seekVideo(time)
        this.$refs.video.play()
       },
       pauseVideo () {
        this.$refs.video.pause()
       },
       stopVideo () {
        this.$refs.video.pause()
        this.$refs.video.currentTime = 0
       },
      },
     }
    </script>
    총결산
    코드 를 쓰 는 것 보다 블 로 그 를 쓰 는 것 이 훨씬 어려워 서 혼 란 스 럽 게 블 로 그 를 다 썼 다.
    몇 개의 작은 디 테 일 목록 을 삭제 할 때의 고도 애니메이션

    UI 는 최대 10 개의 재단 세 션 을 보 여 주 며 초과 하면 스크롤 하고 애니메이션 도 추가 삭제 해 야 한다.직접 설치max-height끝 날 줄 알 았 는데
    CSStransition애니메이션 은 절대 값 의 height 만 유효 하기 때문에 조금 번 거 롭 습 니 다.줄 을 자 르 는 수량 이 변화 하고 높이 도 변화 하기 때 문 입 니 다.절대 치 를 설정 하면 어 떡 하지...
    여기 서 HTML 에 있 는 tag 의attribute속성data-count을 통 해 CSS 에 게 제 가 현재 몇 개의 재단 이 있 는 지 알려 주 고 CSS 가data-count에 따라 목록 높이 를 설정 하도록 합 니 다.
    
    <!--  10      10,     -->
    <div 
     class="crop-time-body"
     :data-count="listLength > 10 ? 10 : listLength -1">
    </div>
    
    
    .crop-time-body {
     overflow-y: auto;
     overflow-x: hidden;
     transition: height .5s;
    
     &[data-count="0"] {
      height: 0;
     }
    
     &[data-count="1"] {
      height: 40px;
     }
    
     &[data-count="2"] {
      height: 80px;
     }
    
     ...
     ...
    
     &[data-count="10"] {
      height: 380px;
     }
    }
    mousemove시 사건 의currentTarget문제
    DOM 이벤트 의 캡 처 와 거품 이 존재 하기 때문에 진도 막대 위 에 시간 스탬프,재단 세 션 등 요소 가 있 을 수 있 습 니 다.mousemove사건 의currentTarget이 변 할 수 있 고 마우스 거리 진도 막대 의 맨 왼쪽offsetX에 문제 가 있 을 수 있 습 니 다.만약 에 검 측currentTarget을 통 해 진도 조 인지 에 도 문제 가 존재 한다.마우스 가 이동 할 때 시간 이 계속 움 직 이기 때문에 가끔 진도 조 에 대응 하 는mousemove사건 을 촉발 하지 못 한다.
    해결 방법 은 페이지 로 딩 이 완 료 된 후에 진도 조 의 가장 왼쪽 에서 페이지 의 가장 왼쪽 거 리 를 얻 는 것 이다.mousemove사건 은 취하 지 않 고offsetX페이지 의 가장 왼쪽clientX을 바탕 으로 하 는 것 이다.그 다음 에 이들 이 감소 하면 마우스 거리 진도 조 의 가장 왼쪽 픽 셀 값 을 얻 을 수 있다.코드 는 위의 추가mousemove감청 에 쓰 여 있 습 니 다.
    시간 포맷
    재단 도 구 는 초 를00:00:00형식의 문자열 로 변환 해 야 하 는 곳 이 많 기 때문에 도구 함 수 를 썼 습 니 다.입력 초,출력dd,HH,mm,ss4 개key를 포함 하 는Object,각각key은 길이 가 2 인 문자열 입 니 다.ES8 의String.prototype.padStart()방법 으로 실현 하 다.
    
    export default function (seconds) {
     const date = new Date(seconds * 1000);
     return {
      days: String(date.getUTCDate() - 1).padStart(2, '0'),
      hours: String(date.getUTCHours()).padStart(2, '0'),
      minutes: String(date.getUTCMinutes()).padStart(2, '0'),
      seconds: String(date.getUTCSeconds()).padStart(2, '0')
     };
    
    }
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기