initial commit (#303)
This commit is contained in:
parent
e446a84ec4
commit
f82957cc56
|
@ -0,0 +1,64 @@
|
|||
# Media Recorder
|
||||
|
||||
## Summary
|
||||
|
||||
Sample SharePoint Framework client-side web part illustrating Video Recording using [MediaRecorder Web API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder).
|
||||
|
||||
This is an experimental web part. Because this technology's specification has not stabilized, check the compatibility table for usage in various browsers. Also note that the syntax and behavior of an experimental technology is subject to change in future versions of browsers as the specification changes
|
||||
|
||||
## Browser with MediaRecorder API support
|
||||
![Sample SharePoint Framework client-side web part illustrating Video Recording using MediaRecorder Web API](./assets/browsersupported.gif)
|
||||
|
||||
## Browser without MediaRecorder API support
|
||||
![Sample SharePoint Framework client-side web part illustrating Video Recording using MediaRecorder Web API](./assets/browserunsupported.gif)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework Developer](https://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
|
||||
* [Office 365 developer tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-developer-tenant)
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
angular-media-recorder|Joseph Velliah (SPRIDER, @sprider)
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|September 04, 2017|Initial release
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- clone this repo
|
||||
- go to the working directory of the webpart folder in the command line run:
|
||||
- npm and typings install
|
||||
- npm install @types/microsoft-ajax --save-dev
|
||||
- npm install @types/sharepoint --save-dev
|
||||
- npm install @types/angular --save-dev
|
||||
- gulp serve
|
||||
- Open the workbench page in a sharepoint site (https://{yoursiteurl}/_layouts/15/workbench.aspx)
|
||||
- add Media Recorder webpart and edit it
|
||||
- configure the library name where you would like to save the recording/input file.
|
||||
- start recording
|
||||
- allow camera and mic
|
||||
- stop recording
|
||||
- replay video
|
||||
- retry or upload the recorded video
|
||||
|
||||
## Features
|
||||
|
||||
This project illustrates the following concepts:
|
||||
- Front/Back camera selection
|
||||
- Video recording using the supported browsers
|
||||
- File uploading option for the unsupported browsers
|
||||
- Playback recording
|
||||
- Upload files to document library
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 671 KiB |
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"entries": [
|
||||
{
|
||||
"entry": "./lib/webparts/mediaRecorder/MediaRecorderWebPart.js",
|
||||
"manifest": "./src/webparts/mediaRecorder/MediaRecorderWebPart.manifest.json",
|
||||
"outputPath": "./dist/media-recorder.bundle.js"
|
||||
}
|
||||
],
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"mediaRecorderStrings": "webparts/mediaRecorder/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "angular-media-recorder",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"solution": {
|
||||
"name": "angular-media-recorder",
|
||||
"id": "ecaddd68-1036-43fb-bdbf-3cce7f764328",
|
||||
"version": "1.0.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/angular-media-recorder.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"port": 4321,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"https": true,
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-case": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-imports": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "angular-media-recorder",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "~1.1.0",
|
||||
"@microsoft/sp-webpart-base": "~1.1.1",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0",
|
||||
"angular": "^1.6.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.1.0",
|
||||
"@microsoft/sp-module-interfaces": "~1.1.0",
|
||||
"@microsoft/sp-webpart-workbench": "~1.1.0",
|
||||
"@types/angular": "^1.6.32",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/microsoft-ajax": "0.0.33",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0",
|
||||
"@types/sharepoint": "^2013.1.7",
|
||||
"gulp": "~3.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface IMediaRecorderWebPartProps {
|
||||
listName: string;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
.mediaRecorder {
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
background-color: #29ACFF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mediaRecorderSection {
|
||||
min-height: 100%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mediaRecorderVideo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mediaRecorderButton {
|
||||
display: block;
|
||||
width: 25%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
height: 3.333em;
|
||||
margin-top: 1.1em;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
|
||||
"id": "784959fa-e575-4b95-8c4c-cb6116f6aefa",
|
||||
"alias": "MediaRecorderWebPart",
|
||||
"componentType": "WebPart",
|
||||
"version": "*", // The "*" signifies that the version should be taken from the package.json
|
||||
"manifestVersion": 2,
|
||||
|
||||
/**
|
||||
* This property should only be set to true if it is certain that the webpart does not
|
||||
* allow arbitrary scripts to be called
|
||||
*/
|
||||
"safeWithCustomScriptDisabled": false,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "784959fa-e575-4b95-8c4c-cb6116f6aefa",
|
||||
"group": { "default": "Under Development" },
|
||||
"title": { "default": "Media Recorder" },
|
||||
"description": { "default": "Sample SharePoint Framework client-side web part illustrating Video Recording using MediaRecorder Web API" },
|
||||
"officeFabricIconFontName": "Video",
|
||||
"properties": {
|
||||
"listName": null
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IWebPartContext,
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import * as strings from 'mediaRecorderStrings';
|
||||
import { IMediaRecorderWebPartProps } from './IMediaRecorderWebPartProps';
|
||||
import { SPComponentLoader } from '@microsoft/sp-loader';
|
||||
import * as angular from 'angular';
|
||||
import './app/app-module';
|
||||
import Home from './app/Home';
|
||||
|
||||
export default class MediaRecorderWebPart extends BaseClientSideWebPart<IMediaRecorderWebPartProps> {
|
||||
|
||||
private $injector: ng.auto.IInjectorService;
|
||||
|
||||
public constructor(context: IWebPartContext) {
|
||||
super();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
if (this.renderedOnce === false) {
|
||||
this.domElement.innerHTML = Home.templateHtml;
|
||||
this.componentDidMount();
|
||||
}
|
||||
|
||||
this.sendWebPartProperties();
|
||||
}
|
||||
|
||||
private componentDidMount(): void {
|
||||
try {
|
||||
|
||||
if (!window["SP"]) {
|
||||
SPComponentLoader.loadScript('/_layouts/15/init.js', {
|
||||
globalExportsName: '$_global_init'
|
||||
})
|
||||
.then((): Promise<{}> => {
|
||||
return SPComponentLoader.loadScript('/_layouts/15/MicrosoftAjax.js', {
|
||||
globalExportsName: 'Sys'
|
||||
});
|
||||
})
|
||||
.then((): Promise<{}> => {
|
||||
return SPComponentLoader.loadScript('/_layouts/15/SP.Core.js', {
|
||||
globalExportsName: 'SP'
|
||||
});
|
||||
})
|
||||
.then((): Promise<{}> => {
|
||||
return SPComponentLoader.loadScript('/_layouts/15/SP.Runtime.js', {
|
||||
globalExportsName: 'SP'
|
||||
});
|
||||
})
|
||||
.then((): Promise<{}> => {
|
||||
return SPComponentLoader.loadScript('/_layouts/15/SP.js', {
|
||||
globalExportsName: 'SP'
|
||||
});
|
||||
})
|
||||
.then((): void => {
|
||||
this.$injector = angular.bootstrap(this.domElement, ['mediarecorderapp']);
|
||||
this.sendWebPartProperties();
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.$injector = angular.bootstrap(this.domElement, ['mediarecorderapp']);
|
||||
this.sendWebPartProperties();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.info("Error in componentDidMount():" + error);
|
||||
}
|
||||
}
|
||||
|
||||
private sendWebPartProperties(): void {
|
||||
if (this.$injector) {
|
||||
this.$injector.get('$rootScope').$broadcast('configurationChanged', {
|
||||
listName: this.properties.listName,
|
||||
httpClient: this.context.spHttpClient,
|
||||
webUrl: this.context.pageContext.web.absoluteUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('listName', {
|
||||
label: strings.ListNameFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
protected get disableReactivePropertyChanges(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { IFile } from "../app/IFile";
|
||||
|
||||
/*=================
|
||||
| Custom Directive to read files from input control |
|
||||
=================*/
|
||||
|
||||
export class CustomFileChange implements ng.IDirective {
|
||||
|
||||
constructor(private $parse: ng.IParseService) {
|
||||
|
||||
}
|
||||
|
||||
public restrict = "A";
|
||||
|
||||
public link = (scope: ng.IScope, element: any, attrs: any) => {
|
||||
|
||||
const model = this.$parse(attrs.customFileChange);
|
||||
const modelSetter = model.assign;
|
||||
|
||||
element.bind("change", (): void => {
|
||||
scope.$apply((): void => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (event: any): void => {
|
||||
const fileModel: IFile = {
|
||||
fileName: element[0].files[0].name,
|
||||
fileAsBuffer: event.target.result
|
||||
};
|
||||
modelSetter(scope, fileModel);
|
||||
};
|
||||
|
||||
reader.onerror = (event: any): void => {
|
||||
console.error(event.target.error);
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(element[0].files[0]);
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static factory(): ng.IDirectiveFactory {
|
||||
const directive = ($parse: ng.IParseService) => new CustomFileChange($parse);
|
||||
directive.$inject = ['$parse'];
|
||||
return directive;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*=================
|
||||
| Service methods |
|
||||
=================*/
|
||||
export interface IDataService {
|
||||
uploadFile(arrayBuffer: any, fileName: string, url: any, listName: string): ng.IPromise<string>;
|
||||
getFileBuffer(file: any): ng.IPromise<any>;
|
||||
arrayBufferToBase64(buffer: any): any;
|
||||
}
|
||||
|
||||
export default class DataService implements IDataService {
|
||||
|
||||
public static $inject: string[] = ['$q', '$http', '$log'];
|
||||
|
||||
constructor(private $q: ng.IQService, private $http: ng.IHttpService, private $log: ng.ILogService)
|
||||
{ }
|
||||
|
||||
/*=================
|
||||
| Upload file to SharePoint |
|
||||
=================*/
|
||||
public uploadFile(arrayBuffer: any, fileName: string, url: any, listName: string): ng.IPromise<string> {
|
||||
|
||||
const deferred: ng.IDeferred<string> = this.$q.defer();
|
||||
|
||||
try {
|
||||
var clientContext = new SP.ClientContext(url);
|
||||
var oWeb = clientContext.get_web();
|
||||
var oList = oWeb.get_lists().getByTitle(listName);
|
||||
var createInfo = new SP.FileCreationInformation();
|
||||
createInfo.set_content(arrayBuffer);
|
||||
createInfo.set_url(fileName);
|
||||
var uploadedDocument = oList.get_rootFolder().get_files().add(createInfo);
|
||||
clientContext.load(uploadedDocument, 'ListItemAllFields');
|
||||
|
||||
clientContext.executeQueryAsync((sender: any, args: SP.ClientRequestSucceededEventArgs): void => {
|
||||
deferred.resolve(uploadedDocument.get_listItemAllFields().get_id().toString());
|
||||
}, (sender: any, args: SP.ClientRequestFailedEventArgs): void => {
|
||||
deferred.reject('Error Message - ' + args.get_message() + ' . Stack Trace - ' + args.get_stackTrace());
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/*=================
|
||||
| File Buffer for the given file |
|
||||
=================*/
|
||||
public getFileBuffer(file: any): ng.IPromise<any> {
|
||||
|
||||
const deferred: ng.IDeferred<any> = this.$q.defer();
|
||||
|
||||
try {
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function (e) {
|
||||
var contents: any = e.target;
|
||||
deferred.resolve(contents.result);
|
||||
};
|
||||
reader.onerror = function (e) {
|
||||
var contents: any = e.target;
|
||||
deferred.reject(contents.error);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
catch (err) {
|
||||
deferred.reject(err);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Convert array buffer to Base64 |
|
||||
=================*/
|
||||
public arrayBufferToBase64(buffer: any): any {
|
||||
|
||||
let binary = '';
|
||||
|
||||
try {
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
catch (err) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
import styles from '../MediaRecorder.module.scss';
|
||||
|
||||
export default class Home {
|
||||
public static templateHtml: string = `
|
||||
<div class="${styles.mediaRecorder}" ng-cloak>
|
||||
<div class="${styles.container}" data-ng-controller="HomeController as hctrl">
|
||||
<div ng-if="hctrl.message">
|
||||
<div class="${styles.mediaRecorderSection}">
|
||||
{{hctrl.message}}
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="hctrl.configurationNeeded && !hctrl.isLoading">
|
||||
<div class="${styles.mediaRecorderSection}">
|
||||
Please configure this webpart
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!hctrl.configurationNeeded">
|
||||
<div class="${styles.mediaRecorderSection}" ng-if="hctrl.isLoading">
|
||||
Loading...
|
||||
</div>
|
||||
<div class="${styles.mediaRecorderSection}" ng-if="!hctrl.mediaRecorderApi">
|
||||
<input value="hctrl.file.fileName" data-custom-file-change="hctrl.file" type="file" accept="video/*" capture="user"/>
|
||||
<button class="${styles.mediaRecorderButton}" ng-model="hctrl.btnFileUpload" data-ng-click="hctrl.handleFileUpload($event)">Upload File</button>
|
||||
</div>
|
||||
<div class="${styles.mediaRecorderSection}" ng-if="hctrl.mediaRecorderApi">
|
||||
<playback></playback>
|
||||
<video class="${styles.mediaRecorderVideo}" ng-if="hctrl.showVideo" ng-model="hctrl.vdRecorder" autoPlay muted ></video>
|
||||
<button class="${styles.mediaRecorderButton}" ng-if="hctrl.showCameraSelection" ng-model="hctrl.frontCamera" ng-click="hctrl.cameraChange($event)">Use Back Camera</button>
|
||||
<button class="${styles.mediaRecorderButton}" ng-if="hctrl.showStart" ng-model="hctrl.btnStart" ng-click="hctrl.handleVideoRecording($event)">Start Recording</button>
|
||||
<button class="${styles.mediaRecorderButton}" ng-if="hctrl.showStop" ng-model="hctrl.btnStop" ng-click="hctrl.handleVideoStop($event)">Stop Recording</button>
|
||||
<button class="${styles.mediaRecorderButton}" ng-if="hctrl.showUpload" ng-model="hctrl.btnUpload" ng-click="hctrl.handleVideoUpload($event)">Upload Recording</button>
|
||||
<button class="${styles.mediaRecorderButton}" ng-if="hctrl.showRetry" ng-model="hctrl.btnRetry" ng-click="hctrl.handleVideoRetry($event)">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
import { IDataService, } from "../app/DataService";
|
||||
import { IFile } from "../app/IFile";
|
||||
import styles from '../MediaRecorder.module.scss';
|
||||
|
||||
export default class HomeController {
|
||||
|
||||
/*=================
|
||||
| WebPart Properties |
|
||||
=================*/
|
||||
private listName: string = null;
|
||||
private webUrl: string = null;
|
||||
|
||||
/*=================
|
||||
| Controller Objects |
|
||||
=================*/
|
||||
|
||||
public file: IFile;
|
||||
|
||||
private fileBlob: any = null;
|
||||
private currentStream: any = null;
|
||||
private recordedBlobs: any = [];
|
||||
private recordedBlobType: any = null;
|
||||
|
||||
private message: string = null;
|
||||
private vdRecorderSrc: string = null;
|
||||
|
||||
private isLoading: boolean = true;
|
||||
private frontCamera: boolean = true;
|
||||
private mediaRecorderApi: boolean = false;
|
||||
private configurationNeeded: boolean = true;
|
||||
private showVideo: boolean = true;
|
||||
private showStart: boolean = true;
|
||||
private showStop: boolean = false;
|
||||
private showUpload: boolean = false;
|
||||
private showRetry: boolean = false;
|
||||
private showCameraSelection: boolean = true;
|
||||
|
||||
public static $inject: string[] = ['DataService', '$window', '$rootScope', '$scope'];
|
||||
|
||||
constructor(private dataService: IDataService, private $window: ng.IWindowService, private $rootScope: ng.IRootScopeService, private $scope: ng.IScope) {
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
/*=================
|
||||
| Property changed event in rootScope|
|
||||
=================*/
|
||||
$rootScope.$on('configurationChanged', (event: ng.IAngularEvent, args: { listName: string; webUrl: string }): void => {
|
||||
hctrl.init(args.listName, args.webUrl);
|
||||
});
|
||||
}
|
||||
|
||||
/*=================
|
||||
| View Initialization |
|
||||
=================*/
|
||||
private init(propListName: string, propWebUrl: string): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
hctrl.isLoading = true;
|
||||
|
||||
hctrl.file = null;
|
||||
hctrl.fileBlob = null;
|
||||
hctrl.currentStream = null;
|
||||
hctrl.recordedBlobs = [];
|
||||
hctrl.recordedBlobType = null;
|
||||
|
||||
hctrl.message = null;
|
||||
hctrl.vdRecorderSrc = null;
|
||||
|
||||
hctrl.frontCamera = true;
|
||||
hctrl.mediaRecorderApi = false;
|
||||
hctrl.configurationNeeded = true;
|
||||
hctrl.showVideo = true;
|
||||
hctrl.showStart = true;
|
||||
hctrl.showStop = false;
|
||||
hctrl.showUpload = false;
|
||||
hctrl.showRetry = false;
|
||||
hctrl.showCameraSelection = true;
|
||||
|
||||
if (propListName != null && propListName.length > 0 &&
|
||||
propWebUrl != null && propWebUrl.length > 0) {
|
||||
hctrl.listName = propListName;
|
||||
hctrl.webUrl = propWebUrl;
|
||||
hctrl.configurationNeeded = false;
|
||||
}
|
||||
else {
|
||||
hctrl.configurationNeeded = true;
|
||||
hctrl.isLoading = false;
|
||||
setTimeout(function () {
|
||||
hctrl.$scope.$apply();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// show/hide relevant video upload buttons depending on browser capabilities
|
||||
if ((window as any).MediaRecorder) {
|
||||
hctrl.mediaRecorderApi = true;
|
||||
hctrl.isLoading = false;
|
||||
console.info('This browser does support the MediaRecorder API.');
|
||||
} else {
|
||||
hctrl.mediaRecorderApi = false;
|
||||
hctrl.isLoading = false;
|
||||
console.info('This browser does not support the MediaRecorder API.');
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
hctrl.$scope.$apply();
|
||||
}, 100);
|
||||
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Start Video Recording |
|
||||
=================*/
|
||||
private handleVideoRecording(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
try {
|
||||
// First get ahold of getUserMedia, if present
|
||||
// Return if browser does not implement to keep a consistent interface
|
||||
if (navigator.mediaDevices.getUserMedia === undefined) {
|
||||
console.info('getUserMedia is not implemented in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
let videoZoneElement = event.srcElement.parentElement;
|
||||
let vdRecorder = videoZoneElement.querySelector('video');
|
||||
|
||||
let constraints = {
|
||||
audio: true,
|
||||
video: {
|
||||
frameRate: { ideal: 10, max: 15 },
|
||||
facingMode: (hctrl.frontCamera ? "user" : "environment")
|
||||
}
|
||||
};
|
||||
|
||||
navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then(function (stream) {
|
||||
hctrl.vdRecorderSrc = window.URL.createObjectURL(stream);
|
||||
vdRecorder.src = hctrl.vdRecorderSrc;
|
||||
let mediaRecorder = new (window as any).MediaRecorder(stream);
|
||||
hctrl.currentStream = null;
|
||||
hctrl.recordedBlobs = [];
|
||||
hctrl.currentStream = stream;
|
||||
mediaRecorder.start(10);
|
||||
mediaRecorder.ondataavailable = function (e) {
|
||||
if (e.data && e.data.size > 0) {
|
||||
hctrl.recordedBlobs.push(e.data);
|
||||
}
|
||||
};
|
||||
|
||||
}).catch(function (err) {
|
||||
console.error("The camera recording request failed with error : " + err);
|
||||
});
|
||||
|
||||
hctrl.showStart = false;
|
||||
hctrl.showStop = true;
|
||||
hctrl.showUpload = false;
|
||||
hctrl.showCameraSelection = false;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The camera recording request failed with error : " + err);
|
||||
}
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Stop Video Recording |
|
||||
=================*/
|
||||
private handleVideoStop(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
try {
|
||||
hctrl.currentStream.getTracks().forEach(function (track) {
|
||||
track.stop();
|
||||
});
|
||||
|
||||
hctrl.recordedBlobType = hctrl.recordedBlobs[0].type;
|
||||
hctrl.fileBlob = new Blob(hctrl.recordedBlobs, { type: hctrl.recordedBlobType });
|
||||
|
||||
let videoZoneElement = event.srcElement.parentElement;
|
||||
if (videoZoneElement) {
|
||||
|
||||
let vdRecorder = videoZoneElement.querySelector('video');
|
||||
if (vdRecorder) {
|
||||
|
||||
if (vdRecorder) {
|
||||
vdRecorder.pause();
|
||||
vdRecorder.src = '';
|
||||
vdRecorder.load();
|
||||
}
|
||||
|
||||
if (hctrl.currentStream && hctrl.currentStream.stop) {
|
||||
hctrl.currentStream.stop();
|
||||
}
|
||||
}
|
||||
|
||||
let videoPlaybackZoneElement = videoZoneElement.querySelector('playback');
|
||||
if (videoPlaybackZoneElement) {
|
||||
let videoPlaybackElement = document.createElement('video');
|
||||
videoPlaybackElement.controls = true;
|
||||
videoPlaybackElement.classList.add(styles.mediaRecorderVideo);
|
||||
videoPlaybackElement.src = window.URL.createObjectURL(hctrl.fileBlob);
|
||||
videoPlaybackZoneElement.appendChild(videoPlaybackElement);
|
||||
videoPlaybackElement.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The camera stop request failed with error : " + err);
|
||||
}
|
||||
finally {
|
||||
hctrl.currentStream = null;
|
||||
hctrl.recordedBlobs = [];
|
||||
hctrl.recordedBlobType = null;
|
||||
hctrl.vdRecorderSrc = null;
|
||||
hctrl.showVideo = false;
|
||||
hctrl.showStart = false;
|
||||
hctrl.showStop = false;
|
||||
hctrl.showUpload = true;
|
||||
hctrl.showRetry = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Chnage Camera |
|
||||
=================*/
|
||||
private cameraChange(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
try {
|
||||
if (event.srcElement) {
|
||||
if (event.srcElement.innerText == "Use Front Camera") {
|
||||
event.srcElement.innerText = "Use Back Camera";
|
||||
hctrl.frontCamera = true;
|
||||
|
||||
} else {
|
||||
event.srcElement.innerText = "Use Front Camera";
|
||||
hctrl.frontCamera = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The camera change request failed with error : " + err);
|
||||
}
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Upload Recording |
|
||||
=================*/
|
||||
private handleVideoUpload(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
hctrl.showUpload = false;
|
||||
hctrl.isLoading = true;
|
||||
|
||||
try {
|
||||
let rand = Math.floor((Math.random() * 10000000));
|
||||
let fileName = "video_" + rand + ".webm";
|
||||
let contents = hctrl.dataService.arrayBufferToBase64(hctrl.fileBlob);
|
||||
|
||||
hctrl.dataService.uploadFile(contents, fileName, hctrl.webUrl, hctrl.listName).then((itemId: string): void => {
|
||||
hctrl.message = "File uploaded Id is : " + itemId + " with name " + fileName;
|
||||
}).catch((err): void => {
|
||||
console.error("The video upload request failed with error : " + err);
|
||||
});
|
||||
|
||||
let videoZoneElement = event.srcElement.parentElement;
|
||||
|
||||
if (videoZoneElement) {
|
||||
let videoPlaybackZoneElement = videoZoneElement.querySelector('playback');
|
||||
|
||||
if (videoPlaybackZoneElement) {
|
||||
let videoPlaybackElement = videoZoneElement.querySelector('video');
|
||||
|
||||
if (videoPlaybackElement) {
|
||||
videoPlaybackElement.parentNode.removeChild(videoPlaybackElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The video upload request failed with error : " + err);
|
||||
}
|
||||
finally {
|
||||
hctrl.fileBlob = null;
|
||||
hctrl.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Upload File |
|
||||
=================*/
|
||||
public handleFileUpload(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
if (!hctrl.file) {
|
||||
hctrl.message = 'Select a file to upload.';
|
||||
return;
|
||||
}
|
||||
|
||||
hctrl.isLoading = true;
|
||||
|
||||
let fileName = hctrl.file.fileName;
|
||||
let fileBuffer = hctrl.file.fileAsBuffer;
|
||||
|
||||
try {
|
||||
hctrl.dataService.uploadFile(fileBuffer, fileName, hctrl.webUrl, hctrl.listName).then((itemId: string): void => {
|
||||
hctrl.message = "File uploaded Id is : " + itemId + " with name " + fileName;
|
||||
}).catch((err): void => {
|
||||
console.error("The file upload request failed with error : " + err);
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The file upload request failed with error : " + err);
|
||||
}
|
||||
finally {
|
||||
hctrl.isLoading = false;
|
||||
hctrl.file = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*=================
|
||||
| Retry |
|
||||
=================*/
|
||||
public handleVideoRetry(event?: any): void {
|
||||
|
||||
const hctrl: HomeController = this;
|
||||
|
||||
try {
|
||||
|
||||
let videoZoneElement = event.srcElement.parentElement;
|
||||
if (videoZoneElement) {
|
||||
|
||||
let videoPlaybackZoneElement = videoZoneElement.querySelector('playback');
|
||||
if (videoPlaybackZoneElement) {
|
||||
|
||||
let videoPlaybackElement = videoZoneElement.querySelector('video');
|
||||
if (videoPlaybackElement) {
|
||||
videoPlaybackElement.parentNode.removeChild(videoPlaybackElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hctrl.init(hctrl.listName, hctrl.webUrl);
|
||||
}
|
||||
catch (err) {
|
||||
console.error("The retry request failed with error : " + err);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface IFile {
|
||||
fileName: string;
|
||||
fileAsBuffer: ArrayBuffer;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import * as angular from 'angular';
|
||||
import HomeController from './HomeController';
|
||||
import DataService from './DataService';
|
||||
import { CustomFileChange } from '../app/customFileChange';
|
||||
|
||||
const mediarecorderapp: ng.IModule = angular.module('mediarecorderapp', []);
|
||||
|
||||
mediarecorderapp
|
||||
.controller('HomeController', HomeController)
|
||||
.directive("customFileChange", CustomFileChange.factory())
|
||||
.service('DataService', DataService);
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Configurations",
|
||||
"BasicGroupName": "Target",
|
||||
"ListNameFieldLabel": "Library Name"
|
||||
}
|
||||
});
|
10
samples/angular-media-recorder/src/webparts/mediaRecorder/loc/mystrings.d.ts
vendored
Normal file
10
samples/angular-media-recorder/src/webparts/mediaRecorder/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface IMediaRecorderStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
ListNameFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'mediaRecorderStrings' {
|
||||
const strings: IMediaRecorderStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('MediaRecorderWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"es6-collections",
|
||||
"webpack-env",
|
||||
"microsoft-ajax",
|
||||
"sharepoint"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Type definitions for Microsoft ODSP projects
|
||||
// Project: ODSP
|
||||
|
||||
/* Global definition for UNIT_TEST builds
|
||||
Code that is wrapped inside an if(UNIT_TEST) {...}
|
||||
block will not be included in the final bundle when the
|
||||
--ship flag is specified */
|
||||
declare const UNIT_TEST: boolean;
|
||||
|
||||
/* Global defintion for SPO builds */
|
||||
declare const DATACENTER: boolean;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
Loading…
Reference in New Issue