web-component-analyzer
web-component-analyzer copied to clipboard
[bug] No useful output from video-player.js
Including here so others can run but this is all I get as output:
command: wca analyze video-player.js --outFile custom-elements.js
output:
#
`video-player`
`A simple responsive video player with ridiculously powerful backing`
Input file of video-player.js
:
/**
* Copyright 2018 The Pennsylvania State University
* @license Apache-2.0, see License.md for full text.
*/
import { html } from "@polymer/polymer/polymer-element.js";
import { SimpleColors } from "@lrnwebcomponents/simple-colors/simple-colors.js";
import { A11yBehaviors } from "@lrnwebcomponents/a11y-behaviors/a11y-behaviors.js";
import "@polymer/polymer/lib/elements/dom-repeat.js";
import "@polymer/polymer/lib/elements/dom-if.js";
import { SchemaBehaviors } from "@lrnwebcomponents/schema-behaviors/schema-behaviors.js";
import { MediaBehaviorsVideo } from "@lrnwebcomponents/media-behaviors/media-behaviors.js";
import "@lrnwebcomponents/a11y-media-player/a11y-media-player.js";
/**
* `video-player`
* `A simple responsive video player with ridiculously powerful backing`
*
* @microcopy - language worth noting:
* - `video source` - url / link to the video file
* ```
<video-player
accent-color$="[[accentColor]]" // Optional accent color for controls,
// using the following materialize colors:
// red, pink, purple, deep-purple, indigo, blue,
// light blue, cyan, teal, green, light green, lime,
// yellow, amber, orange, deep-orange, and brown.
// Default is null.
dark$="[[dark]]" // Is the color scheme dark? Default is light.
dark-transcript$="[[darkTranscript]]" // Use dark theme on transcript? Default is false, even when player is dark.
disable-interactive$="[[disableInteractive]]" // Disable interactive cues?
height$="[[height]]" // The height of player
hide-timestamps$="[[hideTimestamps]]" // Hide cue timestamps?
lang$="[[lang]]" // The language of the media
media-title$="[[mediaTitle]]" // The title of the media
source$="[[source]]" // The source URL of the media
sticky-corner$="[[stickyCorner]]" // When user scrolls a playing video off-screen,
which corner will it stick to? Values are:
top-right (default), top-left, bottom-left, bottom-right,
and none (to turn sticky off)
thumbnail-src$="[[thumbnailSrc]]" // Optional thumbanil/cover image url
width$="[[width]]"> // The width of the media
<div slot="caption">Optional caption info.</div>
</video-player>```
*
* @customElement
* @polymer
* @demo demo/index.html
*/
class VideoPlayer extends MediaBehaviorsVideo(
A11yBehaviors(SchemaBehaviors(SimpleColors))
) {
// render function
static get template() {
return html`
<style>
:host {
display: block;
margin: 0 0 15px;
}
.video-caption {
font-style: italic;
margin: 0;
padding: 8px;
@apply --video-player-caption-theme;
}
</style>
<style include="simple-colors-shared-styles"></style>
<div style$="[[playerStyle]]">
<template is="dom-if" if="[[isA11yMedia]]" restamp>
<a11y-media-player
accent-color$="[[accentColor]]"
audio-only$="[[audioOnly]]"
dark$="[[dark]]"
dark-transcript$="[[darkTranscript]]"
disable-interactive$="[[disableInteractive]]"
hide-timestamps$="[[hideTimestamps]]"
hide-transcript$="[[hideTiranscript]]"
lang$="[[lang]]"
media-type$="[[sourceType]]"
preload$="[[preload]]"
media-title$="[[mediaTitle]]"
sources$="[[sourceData]]"
stand-alone$="[[__standAlone]]"
sticky-corner$="[[stickyCorner]]"
thumbnail-src$="[[thumbnailSrc]]"
tracks$="[[trackData]]"
crossorigin$="[[crossorigin]]"
youtube-id$="[[youtubeId]]"
>
<template
id="sources"
is="dom-repeat"
items="[[sourceData]]"
as="sd"
restamp
>
<source src$="[[sd.src]]" type$="[[sd.type]]" />
</template>
<template
id="tracks"
is="dom-repeat"
items="[[trackData]]"
as="track"
restamp
>
<track
src$="[[track.src]]"
kind$="[[track.kind]]"
label$="[[track.label]]"
srclang$="[[track.lang]]"
/>
</template>
<slot name="caption"></slot>
</a11y-media-player>
</template>
<template is="dom-if" if="[[!isA11yMedia]]">
<template is="dom-if" if="[[sandboxed]]">
<div class="responsive-video-container" lang$="[[lang]]">
<webview
resource$="[[schemaResourceID]]-video"
src$="[[sourceData.0.src]]"
width$="[[width]]"
height$="[[height]]"
frameborder="0"
></webview>
</div>
</template>
<template is="dom-if" if="[[!sandboxed]]">
<template is="dom-if" if="[[iframed]]">
<div class="responsive-video-container" lang$="[[lang]]">
<iframe
resource$="[[schemaResourceID]]-video"
src$="[[sourceData.0.src]]"
width$="[[width]]"
height$="[[height]]"
frameborder="0"
webkitallowfullscreen=""
mozallowfullscreen=""
allowfullscreen=""
></iframe>
</div>
</template>
</template>
<div id="videocaption" class$="video-caption">
<p>
[[mediaTitle]]
<span class="media-type print-only">(embedded media)</span>
</p>
<slot name="caption"></slot>
</div>
</template>
</div>
`;
}
// haxProperty definition
static get haxProperties() {
return {
canScale: true,
canPosition: true,
canEditSource: false,
gizmo: {
title: "Video player",
description:
"This can present video in a highly accessible manner regardless of source.",
icon: "av:play-circle-filled",
color: "red",
groups: ["Video", "Media"],
handles: [
{
type: "video",
source: "source",
title: "caption",
caption: "caption",
description: "caption",
color: "primaryColor"
}
],
meta: {
author: "LRNWebComponents"
}
},
settings: {
quick: [
{
property: "accentColor",
title: "Accent color",
description: "Select the accent color for the player.",
inputMethod: "colorpicker",
icon: "editor:format-color-fill"
},
{
attribute: "dark",
title: "Dark theme",
description: "Enable dark theme for the player.",
inputMethod: "boolean",
icon: "invert-colors"
}
],
configure: [
{
property: "source",
title: "Source",
description: "The URL for this video.",
inputMethod: "haxupload",
icon: "link",
required: true,
validationType: "url"
},
{
property: "track",
title: "Closed captions",
description: "The URL for the captions file.",
inputMethod: "textfield",
icon: "link",
required: true,
validationType: "url"
},
{
property: "thumbnailSrc",
title: "Thumbnail image",
description: "Optional. The URL for a thumbnail/poster image.",
inputMethod: "textfield",
icon: "link",
required: true,
validationType: "url"
},
{
property: "mediaTitle",
title: "Title",
description: "Simple title for under video",
inputMethod: "textfield",
icon: "av:video-label",
required: false,
validationType: "text"
},
{
property: "accentColor",
title: "Accent color",
description: "Select the accent color for the player.",
inputMethod: "colorpicker",
icon: "editor:format-color-fill"
},
{
attribute: "dark",
title: "Dark theme",
description: "Enable dark theme for the player.",
inputMethod: "boolean",
icon: "invert-colors"
}
],
advanced: [
{
property: "darkTranscript",
title: "Dark theme for transcript",
description: "Enable dark theme for the transcript.",
inputMethod: "boolean"
},
{
property: "hideTimestamps",
title: "Hide timestamps",
description: "Hide the time stamps on the transcript.",
inputMethod: "boolean"
},
{
property: "preload",
title: "Preload source(s).",
description:
"How the sources should be preloaded, i.e. auto, metadata (default), or none.",
inputMethod: "select",
options: {
preload: "Preload all media",
metadata: "Preload media metadata only",
none: "Don't preload anything"
}
},
{
property: "stickyCorner",
title: "Sticky Corner",
description:
"Set the corner where a video plays when scrolled out of range, or choose none to disable sticky video.",
inputMethod: "select",
options: {
none: "none",
"top-left": "top-left",
"top-right": "top-right",
"bottom-left": "bottom-left",
"bottom-right": "bottom-right"
}
},
{
property: "sources",
title: "Other sources",
description: "List of other sources",
inputMethod: "array",
properties: [
{
property: "src",
title: "Source",
description: "The URL for this video.",
inputMethod: "textfield"
},
{
property: "type",
title: "Type",
description: "Media type data",
inputMethod: "select",
options: {
"audio/aac": "acc audio",
"audio/flac": "flac audio",
"audio/mp3": "mp3 audio",
"video/mp4": "mp4 video",
"video/mov": "mov video",
"audio/ogg": "ogg audio",
"video/ogg": "ogg video",
"audio/wav": "wav audio",
"audio/webm": "webm audio",
"video/webm": "webm video"
}
}
]
},
{
property: "tracks",
title: "Track list",
description: "Tracks of different languages of closed captions",
inputMethod: "array",
properties: [
{
property: "kind",
title: "Kind",
description: "Kind of track",
inputMethod: "select",
options: {
subtitles:
"subtitles" /*,
Future Features
'description': 'description',
'thumbnails': 'thumbnails',
'interactive': 'interactive',
'annotation': 'annotation'*/
}
},
{
property: "label",
title: "Label",
description:
'The human-readable name for this track, eg. "English Subtitles"',
inputMethod: "textfield"
},
{
property: "src",
title: "Source",
description: "Source of the track",
inputMethod: "textfield"
},
{
property: "srclang",
title:
'Two letter, language code, eg. \'en\' for English, "de" for German, "es" for Spanish, etc.',
description: "Label",
inputMethod: "textfield"
}
]
}
]
}
};
}
// properties available to the custom element for data binding
static get properties() {
let props = {
/**
* Is the media an audio file only?
*/
audioOnly: {
type: Boolean,
value: false
},
/**
* Optional accent color for controls,
* using the following materialize "colors":
* red, pink, purple, deep-purple, indigo, blue,
* light blue, cyan, teal, green, light green, lime,
* yellow, amber, orange, deep-orange, and brown.
* Default is null.
*/
accentColor: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Cross origin flag for transcripts to load
*/
crossorigin: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Enables darker player.
*/
dark: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* Use dark theme on transcript? Default is false, even when player is dark.
*/
darkTranscript: {
type: Boolean,
value: false
},
/**
* disable interactive mode that makes the transcript clickable
*/
disableInteractive: {
type: Boolean,
value: false
},
/**
* The height of the media player for non-a11y-media.
*/
height: {
type: String,
value: null
},
/**
* show cue's start and end time
*/
hideTimestamps: {
type: Boolean,
value: false
},
/**
* hide the transcript by default
*/
hideTranscript: {
type: Boolean,
value: false
},
/**
* Computed if this should be in an iframe or not.
*/
iframed: {
type: Boolean,
computed: "_computeIframed(sourceData, sandboxed)"
},
/**
* Computed if this should be in a11y-media-player.
*/
isA11yMedia: {
type: Boolean,
computed: "_computeA11yMedia(sourceType, sandboxed)"
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
isYoutube: {
type: Boolean,
computed: "_computeYoutube(sourceType)"
},
/**
* The language of the media
*/
lang: {
type: String,
value: "en"
},
/**
* Simple caption for the video
*/
mediaTitle: {
type: String
},
/**
* What to preload for a11y-media-player: auto, metadata (default), or none.
*/
preload: {
type: String,
value: "metadata"
},
/* *
* Responsive video, calculated from not-responsive.
* /
"responsive": {
"type": Boolean,
"reflectToAttribute": true,
"value": true,
},*/
/**
* Compute if this is a sandboxed system or not
*/
sandboxed: {
type: Boolean,
computed: "_computeSandboxed(sourceData)"
},
/**
* Source of the video
*/
source: {
type: String,
value: null,
reflectToAttribute: true
},
/**
* Source of the video
*/
sources: {
type: Array,
value: []
},
/**
* List of source objects
*/
sourceData: {
type: Array,
computed: "_getSourceData(source,sources,trackData)"
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
sourceType: {
type: String,
computed: "_computeSourceType(sourceData)"
},
/**
* When playing but scrolled off screen, to which corner does it "stick":
* top-left, top-right, bottom-left, bottom-right, or none?
* Default is "top-right". "None" disables stickiness.
*/
stickyCorner: {
type: String,
value: "top-right",
reflectToAttribute: true
},
/**
* The url for a single subtitle track
*/
track: {
type: String,
value: null
},
/**
* Array of text tracks
* [{
* "src": "path/to/track.vtt",
* "label": "English",
* "srclang": "en",
* "kind": "subtitles",
* }]
*/
tracks: {
type: Array,
value: []
},
/**
* Cleaned array of text tracks
* [{
* "src": "path/to/track.vtt",
* "label": "English",
* "srclang": "en",
* "kind": "subtitles",
* }]
*/
trackData: {
type: Array,
computed: "_getTrackData(track,tracks)"
},
/**
* Source of optional thumbnail image
*/
thumbnailSrc: {
type: String,
value: null,
reflectToAttribute: true
},
/* *
* Calculate vimeo color based on accent color.
* /
"vimeoColor": {
"type": String,
"computed": getVimeoColor(dark,accentColor),
},
*/
/**
* The width of the media player for non-a11y-media.
*/
width: {
type: String,
value: null
},
/**
* The type of source, i.e. "local", "vimeo", "youtube", etc.
*/
youtubeId: {
type: String,
computed: "_computeYoutubeId(source,sourceType)"
}
};
if (super.properties) {
props = Object.assign(props, super.properties);
}
return props;
}
/**
* Store the tag name to make it easier to obtain directly.
* @notice function name must be here for tooling to operate correctly
*/
static get tag() {
return "video-player";
}
/**
* life cycle, element is afixed to the DOM
*/
connectedCallback() {
super.connectedCallback();
}
/**
* Get Youtube ID
*/
_computeYoutubeId(source, sourceType) {
if (source !== undefined && sourceType === "youtube") {
return this._computeSRC(source).replace(
/(https?:\/\/)?(www.)?youtube(-nocookie)?.com\/embed\//,
""
);
}
return false;
}
/**
* Determine if it is youtube
*/
_computeYoutube(sourceType) {
return sourceType === "youtube";
}
/**
* Determine if it is compatible with a11y-media-player
*/
_computeA11yMedia(sourceType, sandboxed) {
if (!sandboxed && (sourceType == "youtube" || sourceType == "local")) {
return true;
}
return false;
}
/**
* Compute iframed status
*/
_computeIframed(sourceData, sandboxed) {
// make sure we take into account sandboxing as well
// so that we can manage the state effectively
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
window.MediaBehaviors.Video._sourceIsIframe(sourceData[0].src) &&
!sandboxed
) {
return true;
}
return false;
}
/**
* Gets cleaned track list
*/
_getTrackData(track, tracks) {
let temp =
typeof tracks === "string" ? JSON.parse(tracks).slice() : tracks.slice();
if (track !== undefined && track !== null && track !== "")
temp.push({
src: track,
srclang: this.lang,
label: this.lang === "en" ? "English" : this.lang,
kind: "subtitles"
});
return temp;
}
/**
* Gets source and added to sources list
*/
_getSourceData(source, sources, trackData) {
if (typeof sources === "string") sources = JSON.parse(sources);
let root = this,
temp = sources.slice();
for (let i = 0; i < temp.length; i++) {
temp[i].type =
temp[i].type !== undefined && temp[i].type !== null
? temp[i].type
: this._computeMediaType(temp[i].src);
temp[i].src = this._computeSRC(temp[i].src);
}
if (source !== null) {
let src = this._computeSRC(source);
this.sourceType = this._computeSourceType(src);
if (this.sourceType !== "youtube") {
temp.unshift({ src: src, type: this._computeMediaType(src) });
}
}
this.__standAlone =
trackData === undefined || trackData === null || trackData.length < 1;
return temp;
}
/**
* Compute media type based on source, i.e. 'audio/wav' for '.wav'
*/
_computeMediaType(source) {
let root = this,
audio = ["aac", "flac", "mp3", "oga", "wav"],
video = ["mov", "mp4", "ogv", "webm"],
type = "",
findType = function(text, data) {
for (let i = 0; i < data.length; i++) {
if (
type === "" &&
source !== undefined &&
source !== null &&
source.toLowerCase().indexOf("." + data[i]) > -1
) {
if (text === "audio") root.audioOnly = true;
type = text + "/" + data[i];
}
}
};
findType("audio", audio);
findType("video", video);
return type;
}
/**
* Compute sandboxed status
*/
_computeSandboxed(sourceData) {
// we have something that would require an iframe
// see if we have a local system that would want to sandbox instead
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
window.MediaBehaviors.Video._sourceIsIframe(sourceData[0].src)
) {
// fake the creation of a webview element to see if it's valid
// or not.
let test = document.createElement("webview");
// if this function exists it means that our deploy target
// is in a sandboxed environment and is not able to run iframe
// content with any real stability. This is beyond edge case but
// as this is an incredibly useful tag we want to make sure it
// can mutate to work in chromium and android environments
// which support such sandboxing
if (typeof test.reload === "function") {
return true;
}
}
return false;
}
/**
* Compute video type based on source
*/
_computeSourceType(sourceData) {
let root = this;
if (
sourceData.length > 0 &&
sourceData[0] !== undefined &&
typeof sourceData[0].src !== typeof undefined
) {
return window.MediaBehaviors.Video.getVideoType(sourceData[0].src);
} else {
return null;
}
}
/**
* Compute src from type / source combo.
* Type is set by source so this ensures a waterfall
* of valid values.
*/
_computeSRC(source) {
if (source !== null && typeof source !== undefined) {
let type =
this.sourceType !== undefined
? this.sourceType
: window.MediaBehaviors.Video.getVideoType(source);
// ensure that this is a valid url / cleaned up a bit
source = window.MediaBehaviors.Video.cleanVideoSource(source, type);
if (type == "vimeo") {
if (this.vimeoTitle) {
source += "?title=1";
} else {
source += "?title=0";
}
if (this.vimeoByline) {
source += "&byline=1";
} else {
source += "&byline=0";
}
if (this.vimeoPortrait) {
source += "&portrait=1";
} else {
source += "&portrait=0";
}
if (typeof this.videoColor !== typeof undefined) {
source += "&color=" + this.videoColor;
}
} else if (type == "dailymotion") {
source += "&ui-start-screen-info=false";
source += "&ui-logo=false";
source += "&sharing-enable=false";
source += "&endscreen-enable=false";
if (typeof this.videoColor !== typeof undefined) {
source += "&ui-highlight=" + this.videoColor;
}
}
}
return source;
}
/**
* postProcesshaxNodeToContent - clean up so we don't have empty array data
*/
postProcesshaxNodeToContent(content) {
content = content.replace(' sources="[]",', "");
content = content.replace(' tracks="[]",', "");
return content;
}
}
window.customElements.define(VideoPlayer.tag, VideoPlayer);
export { VideoPlayer };
is this because we use a custom base class and or class mixins?
web-component-analyzer supports class mixins, so your code should work.
I have two suspicions:
- The source files with mixin function declarations are not available for the analyzer.
- A mixin function uses a syntax that I do not yet support.
Two questions:
- What's the output if you run the CLI with
--debug
? - How does one of the mixin functions look like?
wca analyze video-player.js --outFile custom-elements.js --debug
{ _: [ 'analyze', 'video-player.js' ],
outFile: 'custom-elements.js',
debug: true }
[ '/Users/bto108/Documents/git/elmsln/company/factories/lrnwebcomponents/elements/video-player/video-player.js' ]
Analyzing 1 file...
[ { fileName:
'/Users/bto108/Documents/git/elmsln/company/factories/lrnwebcomponents/elements/video-player/video-player.js',
tags:
[ { fileName:
'/Users/bto108/Documents/git/elmsln/company/factories/lrnwebcomponents/elements/video-player/video-player.js',
tagName: '',
description:
'`video-player`\n`A simple responsive video player with ridiculously powerful backing`',
deprecated: undefined,
members: [],
slots: [],
events: [],
cssProperties: [] } ],
events: [] } ]
Writing result to "custom-elements.js"
from SchemaBehaviors.js:
/**
* Copyright 2018 The Pennsylvania State University
* @license Apache-2.0, see License.md for full text.
*/
export const SchemaBehaviors = function(SuperClass) {
return class extends SuperClass {
static get properties() {
let props = {
/**
* Unique Resource ID, generated when schemaMap processes.
*/
schemaResourceID: {
type: String,
value: ""
},
/**
* Schema Map for the element, used to generate a valid prefix on the fly
*/
schemaMap: {
type: Object,
readOnly: true,
value: {
prefix: {
oer: "http://oerschema.org/",
schema: "http://schema.org/",
dc: "http://purl.org/dc/terms/",
foaf: "http://xmlns.com/foaf/0.1/",
cc: "http://creativecommons.org/ns#",
bib: "http://bib.schema.org"
}
},
observer: "_schemaMapChanged"
}
};
if (super.properties) {
props = Object.assign(props, super.properties);
}
return props;
}
/**
* Generate a uinque ID
*/
generateResourceID() {
function idPart() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return (
"#" +
idPart() +
idPart() +
"-" +
idPart() +
"-" +
idPart() +
"-" +
idPart()
);
}
/**
* Notice the schema map has changed, reprocess attributes.
*/
_schemaMapChanged(newValue, oldValue) {
if (typeof newValue !== typeof undefined) {
// use this to tie into schemaResourceID build
this.schemaResourceID = this.getAttribute("resource");
// if it still doesn't have one then we have to check
if (this.schemaResourceID == "" || this.schemaResourceID == null) {
this.schemaResourceID = this.generateResourceID();
this.setAttribute("resource", this.schemaResourceID);
}
let prefixes = newValue.prefix;
let prefix = "";
// build prefix string
for (var property in prefixes) {
if (prefixes.hasOwnProperty(property)) {
prefix += property + ":" + prefixes[property] + " ";
}
}
// set prefix on the main element itself
if (prefix != "") {
this.setAttribute("prefix", prefix);
}
}
}
};
};
Thanks!
I tried to copy in your code, and WCA seems to finds the mixins. What happens if you add the following code to the files?
- Add this to
video-player.js
constructor () {
super();
this.foo = "bar";
}
- and add this to
SchemaBehaviors.js
static get observedAttributes () {
return ["bar"];
}
In my test it found both the property "foo" and the attribute "bar", but the files were in the same project. Therefore, I want to test if WCA is finding your source files. The source file comes from the dependency @lrnwebcomponents/schema-behaviors/schema-behaviors.js
, and I think the underlying (Typescript) parser is not finding them because it expects declaration files for libraries.
In addition, right now, the tool doesn't support assigning properties to a variable and then returning them (see static get properties()
in SchemaBehavior.js
), so I think that bug should be opened as a separate issue :-)