diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 7d45083fa9f56ca0b7c36c61b76da8b6e143eac4..efe0b91ed11986671d62c62ab3d505eb5fd9d226 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -21,6 +21,9 @@ import { registerLocaleData } from '@angular/common';
 import zh from '@angular/common/locales/zh';
 import {UploadImageWithPreviewComponent} from "./common/upload-image-with-preview/upload-image-with-preview.component";
 import {BackgroundImagePipe} from "./pipes/background-image.pipe";
+import {UploadVideoComponent} from "./common/upload-video/upload-video.component";
+import {ResourcePipe} from "./pipes/resource.pipe";
+import {TimePipe} from "./pipes/time.pipe";
 registerLocaleData(zh);
 
 @NgModule({
@@ -32,6 +35,9 @@ registerLocaleData(zh);
     AudioRecorderComponent,
     UploadImageWithPreviewComponent,
     BackgroundImagePipe,
+    ResourcePipe,
+    TimePipe,
+    UploadVideoComponent,
 
     PlayerContentWrapperComponent
   ],
diff --git a/src/app/common/upload-video/upload-video.component.html b/src/app/common/upload-video/upload-video.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e39116aaf958867cde66a3076e2b11538a86ab77
--- /dev/null
+++ b/src/app/common/upload-video/upload-video.component.html
@@ -0,0 +1,95 @@
+
+<div class="up-video" style="display: flex;">
+  <!--<nz-upload class="" [nzDisabled]="!showUploadBtn"-->
+               <!--[nzShowUploadList]="false"-->
+               <!--[nzBeforeUpload]="beforeUpload"-->
+               <!--nzAccept = ".mp4"-->
+               <!--style="margin-right: 1rem">-->
+
+    <nz-upload class="p-image-uploader" [nzDisabled]="uploading"
+               [nzShowUploadList]="false"
+               nzAccept = ".mp4"
+               [nzAction]="uploadUrl"
+               [nzData]="uploadData"
+               (nzChange)="handleChange($event)"
+               style="margin-right: 1rem">
+
+      <button type="button" nz-button nzType="default" *ngIf="showUploadBtn" [disabled]="uploading"
+              [nzLoading]="uploading" >
+        <i nz-icon type="plus" theme="outline"></i>
+
+        <span>{{ uploading ? 'Uploading' : 'Select Video' }}</span>
+        <!--<span>Select Video</span>-->
+      </button>
+
+
+
+
+    </nz-upload>
+  <!--<button nz-button nzType="primary"  *ngIf="showUploadBtn"-->
+            <!--style="margin-right: 1rem"-->
+            <!--type="button"-->
+            <!--[nzLoading]="uploading"-->
+            <!--(click)="handleUpload()"-->
+            <!--[disabled]="!videoFile || uploading">-->
+      <!--<i nz-icon type="upload" theme="outline"></i>-->
+      <!--<span>{{ uploading ? 'Uploading' : 'Start Upload' }}</span>-->
+    <!--</button>-->
+
+    <!--
+    <button type="button"
+            (click)="extraCover()"
+            nz-button nzType="default"
+            [disabled]=" !(videoUrl && videoUrl.constructor.name === 'String')">设置封面</button>
+    -->
+    <!--
+   <nz-divider></nz-divider>
+
+   <div [style.display]="!uploading?'none':''" style="position:relative">
+     <div style="width: calc( 100% - 20px);">
+       <nz-progress [nzPercent]="progress">
+       </nz-progress>
+     </div>
+     <i (click)="cancelUpload()" class="anticon anticon-close-circle" style="position: absolute;top: 50%;right: 0;transform: translateY(-50%);cursor: pointer"></i>
+   </div>
+   -->
+
+
+</div>
+<div class="p-box d-flex align-items-center p-video-uploader" style="top: 20px;">
+  <div class="p-upload-icon" *ngIf="!showUploadBtn && !videoUrl && !uploading">
+    <i nz-icon type="upload" theme="outline"></i>
+    <div class="m-3"></div>
+    <span>Click here to upload video</span>
+    <div class="mt-5 p-progress-bar" *ngIf="uploading">
+      <div class="p-progress-bg" [style.width]="progress*0.2+'rem'"></div>
+      <div class="p-progress-value">{{progress}}%</div>
+    </div>
+  </div>
+  <div class="p-upload-progress-bg dddd " *ngIf="uploading"
+       [ngClass]="{'smart-bar': showUploadBtn}" >
+    <div class="i-bg" [style.width]="progress+'%'"></div>
+    <div class="i-text">
+      <fa name="cloud-upload"></fa>
+      Uploading...
+    </div>
+  </div>
+  <div class="p-upload-check-bg" *ngIf="checking">
+    <div class="i-bg" [style.width]="progress+'%'"></div>
+    <div class="i-text">
+      <fa name="cloud-upload"></fa>
+      <i nz-icon type="loading" theme="outline"></i>Checking...
+    </div>
+  </div>
+  <div class="p-preview" *ngIf="!showUploadBtn && !uploading && videoUrl " >
+    <!--<video crossorigin="anonymous" [src]="videoUrl" controls #videoNode></video>-->
+    <video [src]="safeVideoUrl(videoUrl)" controls #videoNode (loadedmetadata)="videoLoadedMetaData()"></video>
+  </div>
+</div>
+<div [style.display]="!checkVideoExists?'none':''">
+  <span><i nz-icon type="loading" theme="outline"></i>  checking file to upload</span>
+</div>
+
+
+
+
diff --git a/src/app/common/upload-video/upload-video.component.scss b/src/app/common/upload-video/upload-video.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b6664069c2a89349f8338503aaeefde1aa7e4c0e
--- /dev/null
+++ b/src/app/common/upload-video/upload-video.component.scss
@@ -0,0 +1,130 @@
+@import '../../style/common_mixin';
+
+.p-video-uploader {
+  position: relative;
+  display: block;
+  width: 100%;
+  height: 0;
+  padding-bottom: 56.25%;
+  .p-box {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    border: 2px dashed #ddd;
+    //border-radius: 0.5rem;
+    background-color: #fafafa;
+    text-align: center;
+    color: #aaa;
+    .p-upload-icon {
+      text-align: center;
+      margin: auto;
+      .anticon-upload {
+        color: #888;
+        font-size: 5rem;
+      }
+      .p-progress-bar {
+        position: relative;
+        width: 20rem;
+        height: 1.5rem;
+        border: 1px solid #ccc;
+        border-radius: 1rem;
+        .p-progress-bg {
+          background-color: #1890ff;
+          border-radius: 1rem;
+          height: 100%;
+        }
+        .p-progress-value {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          height: 100%;
+          text-shadow: 0 0 4px #000;
+          color: white;
+          text-align: center;
+          font-size: 0.9rem;
+          line-height: 1.5rem;
+        }
+      }
+    }
+    .p-preview {
+      width: 100%;
+      height: 100%;
+      //background-image: url("https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png");
+      video{
+        max-height: 100%;
+        max-width: 100%;
+      }
+    }
+  }
+}
+
+.p-btn-delete {
+  position: absolute;
+  right: -0.5rem;
+  top: -0.5rem;
+  width: 2rem;
+  height: 2rem;
+  border: 0.2rem solid white;
+  border-radius: 50%;
+  font-size: 1.2rem;
+  background-color: #fb781a;
+  color: white;
+  text-align: center;
+}
+
+.p-upload-progress-bg, .p-upload-check-bg {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .i-text {
+    position: absolute;
+    text-align: center;
+    color: white;
+    text-shadow: 0 0 2px rgba(0, 0, 0, .85);
+  }
+  & .i-bg {
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    background-color: #1890ff;
+    //border-radius: 0.5rem 0 0 0.5rem;
+  }
+}
+.p-upload-progress-bg.smart-bar{
+  height: 50px!important;
+  margin-top: 20px!important;
+}
+.p-upload-check-bg {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .i-text {
+    position: absolute;
+    text-align: center;
+    color: white;
+    text-shadow: 0 0 2px rgba(0, 0, 0, .85);
+  }
+  & .i-bg {
+    position: absolute;
+    left: 0;
+    top: 0;
+    height: 100%;
+    background-color: #17a2b8;
+    //border-radius: 0.5rem 0 0 0.5rem;
+  }
+}
+
+
+//:host ::ng-deep .ant-upload {
+//  display: block;
+//}
diff --git a/src/app/common/upload-video/upload-video.component.ts b/src/app/common/upload-video/upload-video.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b947e15cf3ca045f10becd3be01a73e36d8f6c93
--- /dev/null
+++ b/src/app/common/upload-video/upload-video.component.ts
@@ -0,0 +1,201 @@
+import {Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SecurityContext, ViewChild} from '@angular/core';
+import {NzMessageService, UploadFile} from 'ng-zorro-antd';
+import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
+
+
+@Component({
+  selector: 'app-upload-video',
+  templateUrl: './upload-video.component.html',
+  styleUrls: ['./upload-video.component.scss']
+})
+export class UploadVideoComponent implements OnChanges, OnDestroy {
+  uploading = false;
+  checking = false;
+  checkVideoExists = false;
+  uploadClicked = false;
+
+  @Input()
+  videoFile = null;
+  uploaderInst = null;
+
+  progress = 0;
+  // @Input()
+  // setCovering = false;
+
+  @Input()
+  videoUrl = '';
+  @ViewChild('videoNode')
+  videoNode: ElementRef;
+
+
+
+
+  // @Input()
+  // extractCoverFunc = null;
+
+  @Output()
+  videoUploaded = new EventEmitter();
+  @Output()
+  videoUploadFailure = new EventEmitter();
+  @Output()
+  extractVideoCover = new EventEmitter();
+
+
+  @Input()
+  showUploadBtn = true;
+  @Input()
+  item: any;
+  // videoItem = null;
+
+  uploadUrl = (<any> window).courseware.uploadUrl();
+  uploadData = (<any> window).courseware.uploadData();
+
+  constructor(private nzMessageService: NzMessageService,
+              private sanitization: DomSanitizer
+              // private cwService: _coursewareService,
+              // private resService: ResourceService
+              ) {
+    this.uploading = false;
+    this.videoFile = null;
+
+  }
+  ngOnChanges() {
+    // if (!this.videoFile || this.showUploadBtn) {
+    //   return;
+    // }
+    // this.beforeUpload(this.videoFile);
+    // this.handleUpload();
+  }
+  ngOnDestroy(): void {
+    URL.revokeObjectURL(this.videoUrl);
+  }
+
+  safeVideoUrl(url) {
+    console.log(url)
+    return this.sanitization.bypassSecurityTrustResourceUrl(url); // `${url}`;
+  }
+  videoLoadedMetaData() {
+
+  }
+
+
+  handleChange(info: { type: string, file: UploadFile, event: any }): void {
+
+    console.log('info:' , info);
+
+    switch (info.type) {
+      case 'start':
+
+        // this.beforeUpload(info.file);
+
+
+        if (!this.checkSelectFile(info.file)) {
+          return;
+        }
+
+
+        this.uploading = true;
+        this.progress = 0;
+
+        break;
+
+      case 'success':
+
+
+        this.uploadSuccess(info.file);
+
+        // this.beforeUpload(info.file);
+        // this.uploadSuccess(info.file);
+
+        break;
+      case 'progress':
+        this.progress = parseInt(info.event.percent, 10);
+        this.doProgress(this.progress);
+        break;
+    }
+  }
+
+
+  checkSelectFile(file) {
+    if (!file.lastModified) {
+      return false;
+    }
+    const delta = 500;
+    const isOverSize = (file.size / 1024 / 1024) < delta;
+    if (!isOverSize) {
+      this.nzMessageService.error(`video must smaller than ${delta}MB!`);
+      return false;
+    }
+    return true;
+  }
+  checkHashFinish = (hash) => {
+    this.checking = false;
+    this.uploading = true;
+  }
+
+  uploadSuccess = (file) => {
+
+    this.nzMessageService.info('Upload Success');
+    // this.updateStatus(false);
+    this.uploading = false;
+    this.videoFile = null;
+    // this.updateSource(url);
+    this.videoUrl = file.response.url;
+    // console.log(this.picUrl)
+    // this.uploadFinished(url);
+    // if (!inOSS) {
+      const vid = document.createElement('video');
+      vid.addEventListener('loadedmetadata', () => {
+        const height = vid.videoHeight;
+        const width = vid.videoWidth;
+        let duration = vid.duration;
+        if (duration) {
+          duration = duration * 1000;
+        }
+        file['height'] = height;
+        file['width'] = width;
+        file['duration'] = duration;
+        vid.preload = 'none';
+        vid.src = '';
+        vid.remove();
+
+        this.videoUploaded.emit(file.response);
+
+        // this.resService.updateVideo(id, {width, height, duration}).then( () => {
+        //   this.videoUploaded.emit({res_id: id, id, name, hash, url});
+        // });
+
+      });
+      vid.src = file.response.url;
+    // } else {
+    //   this.videoUploaded.emit(file.response);
+    // }
+
+
+
+  }
+
+
+
+  uploadFailure = (err, file) => {
+    this.uploading = false;
+    if (err.name && err.name === 'cancel') {
+      return;
+    }
+    console.log(err);
+    this.nzMessageService.error('Upload Error ' + err.message);
+    this.videoUploadFailure.emit(file);
+  }
+
+  doProgress =  (p) => {
+    if (p > 1) {
+      p = 1;
+    }
+    if (p < 0) {
+      p = 0;
+    }
+    // console.log(Math.floor(p * 100));
+    this.progress =  Math.floor(p * 100);
+  }
+}
+
diff --git a/src/app/pipes/resource.pipe.ts b/src/app/pipes/resource.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d7f5f8df579c3d7981a2556ef149990a9d6ba50b
--- /dev/null
+++ b/src/app/pipes/resource.pipe.ts
@@ -0,0 +1,28 @@
+import {Pipe, PipeTransform} from '@angular/core';
+// import {ElectronUtil} from '../util/ElectronUtil';
+
+declare const APP_PATH;
+// const isInElectron = localStorage.getItem('electron');
+
+@Pipe({
+  name: 'resource'
+})
+export class ResourcePipe implements PipeTransform {
+
+  transform(pic_url: any, coursewareSid?: string): string {
+    // return pic_url;
+    if (pic_url &&  typeof pic_url === 'object') {
+      return pic_url;
+    }
+    // console.log('resource', pic_url)
+    return `${pic_url}?t=${Math.random()}`;
+
+    // let ret = value ? `/api/resource/${value}/file` : '';
+    // let ret = res_id ? `/resource/audio/${res_id}` : '';
+    // if (isInElectron && ElectronUtil.hasCached(coursewareSid, res_id)) {
+    //   ret = `file://${APP_PATH}/courseware_cache/${coursewareSid}/${res_id}`;
+    // }
+    // return ret;
+  }
+
+}
diff --git a/src/app/pipes/time.pipe.ts b/src/app/pipes/time.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..af84a0c697319c7f0c1f41115a113a6f4540d423
--- /dev/null
+++ b/src/app/pipes/time.pipe.ts
@@ -0,0 +1,19 @@
+import {Pipe, PipeTransform} from '@angular/core';
+import * as _ from 'lodash';
+
+@Pipe({
+  name: 'time'
+})
+export class TimePipe implements PipeTransform {
+
+  transform(value: any, args?: any): any {
+    let ret = '00:00';
+    if (_.isNumber(value) && value > 0) {
+      const minutes = Math.floor(value / 60);
+      const seconds = Math.floor(value % 60);
+      ret = _.padStart(minutes, 2, '0') + ':' + _.padStart(seconds, 2, '0');
+    }
+    return ret;
+  }
+
+}
diff --git a/src/assets/default/video-not-upload.jpg b/src/assets/default/video-not-upload.jpg
new file mode 100755
index 0000000000000000000000000000000000000000..31fbbcc7703958c0153d883aeded25e2a53e0542
Binary files /dev/null and b/src/assets/default/video-not-upload.jpg differ
diff --git a/src/index.html b/src/index.html
index df57d466a8da6efea2687c67f79ca8c22bc08f51..b77274e92ddffbd8103ae2b3f181ef27f6e95ba9 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8,81 +8,81 @@
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
 
-  <script>  
+  <script>
 
-      window.courseware = (function(){
-        if(window.parent.net){
-          window.net = window.parent.net; 
-          var id = window.frameElement.getAttribute("dataid"); 
-          return {
-            getData: function(callback){
-              net.getData("getCoursewareData",id,function(res){
-                if(res){
-                  res = JSON.parse(res);
-                  if(res.msg==="success"){
-                    let callData = null;
-                    if(res.data&&res.data!='null'){
-                      callData = JSON.parse(res.data);
-                    }
-                    callback&&callback(callData);  
-                  }else{
-                    alert('数据加载失败!');
+    window.courseware = (function(){
+      if(window.parent.net){
+        window.net = window.parent.net;
+        var id = window.frameElement.getAttribute("dataid");
+        return {
+          getData: function(callback, key=''){
+            net.getData("getCoursewareData",id,function(res){
+              if(res){
+                res = JSON.parse(res);
+                if(res.msg==="success"){
+                  let callData = null;
+                  if(res.data&&res.data!='null'){
+                    callData = JSON.parse(res.data);
                   }
+                  callback&&callback(callData);
                 }else{
                   alert('数据加载失败!');
                 }
-              });
-            },
-            setData: function(data,callback){
-              let str = JSON.stringify(data);
-              net.getData("setCoursewareData",{id: id,data: str},function(res){
-                if(res){
-                  res = JSON.parse(res);
-                  if(res.msg==="success"){
-                    callback&&callback();
-                  }else{
-                    alert('数据保存失败!');
-                  }
+              }else{
+                alert('数据加载失败!');
+              }
+            });
+          },
+          setData: function(data,callback, key=''){
+            let str = JSON.stringify(data);
+            net.getData("setCoursewareData",{id: id,data: str},function(res){
+              if(res){
+                res = JSON.parse(res);
+                if(res.msg==="success"){
+                  callback&&callback();
                 }else{
                   alert('数据保存失败!');
                 }
-              });
-            },
-            uploadUrl: function(){
-              return net.getUploadFileURL();
-            },
-            uploadData: function(){
-              return net.getAjaxData("uploadFile","");
-            }
-          }
-        } else {
-          return {
-            getData: function(callback){
-              let data = localStorage.getItem("courseware_data");
-              if(data){
-                data = JSON.parse(data);
+              }else{
+                alert('数据保存失败!');
               }
-              callback&&callback(data);
-            },
-            setData: function(data,callback){
-              console.log("******local********"); 
-              localStorage.setItem("courseware_data",JSON.stringify(data));
-              callback&&callback();
-            },
-            uploadUrl: function(){
-              return 'http://localhost:3000/api/test';
-            },
-            uploadData: function(){
-              return {};
+            });
+          },
+          uploadUrl: function(){
+            return net.getUploadFileURL();
+          },
+          uploadData: function(){
+            return net.getAjaxData("uploadFile","");
+          }
+        }
+      } else {
+        return {
+          getData: function(callback, key=''){
+            let data = localStorage.getItem("courseware_data_" + key);
+            if(data){
+              data = JSON.parse(data);
             }
+            callback&&callback(data);
+          },
+          setData: function(data,callback, key=''){
+            console.log("******local********");
+            localStorage.setItem("courseware_data_" + key,JSON.stringify(data));
+            callback&&callback();
+          },
+          uploadUrl: function(){
+            return 'http://localhost:3000/api/test';
+          },
+          uploadData: function(){
+            return {};
           }
         }
-      })();
-    
-      
-    </script>
+      }
+    })();
+
+
+  </script>
 </head>
 <body>
-  <app-root></app-root>
-</body> 
+<app-root></app-root>
+</body>
 </html>
diff --git a/tslint.json b/tslint.json
index f29eef2d3fc70cfde3cd319bc502e678604f4095..4fcb4b1d413f773db7ea534e2c9d6d29436a819a 100644
--- a/tslint.json
+++ b/tslint.json
@@ -89,7 +89,10 @@
     "use-lifecycle-interface": true,
     "use-pipe-transform-interface": true,
     "no-trailing-whitespace": false,
-    "variable-name": false
+    "variable-name": false,
+
+    "no-unused-expression": false,
+    "align": false
   },
   "rulesDirectory": [
     "codelyzer"