From c943bf2f3b281413a7a9ec0a71265c98d789ffee Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Mon, 5 Jun 2023 07:54:01 -0400 Subject: [PATCH 001/460] Add warning to top of readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3df3d34c9..971ed7e35 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ A superset of Roku's BrightScript language. Compiles to standard BrightScript. ## Overview +**WARNING: this is the v0.66.0 branch with experimental features** + The BrighterScript language provides new features and syntax enhancements to Roku's BrightScript language. Because the language is a superset of BrightScript, the parser and associated tools (VSCode integration, cli, etc...) work with standard BrightScript (.brs) files. This means you will get benefits (as described in the following section) from using the BrighterScript compiler, whether your project contains BrighterScript (.bs) files or not. The BrighterScript language transpiles to standard BrightScript, so your code is fully compatible with all roku devices. ## Features From f144743e517322ea495bea137f53d1fc3db7726c Mon Sep 17 00:00:00 2001 From: Bronley Plumb Date: Tue, 6 Jun 2023 13:46:31 -0400 Subject: [PATCH 002/460] Type Tracking (#783) * Add abstract TypeExpression class * Convert type tokens to TypeExpressions (#785) * Convert type tokens to TypeExpressions * Fixed all tests * Rebased and fixed any other merge issues * Further work on SymbolTypeFlags * Lint fixes * Fixed issue with using enums as variables * Uses complete TypeName as Custom type for TypeExpression.getType() * Removed commented out code, fixed tests * Fixes from PR review * Fixed lint issues --------- Co-authored-by: Mark Pearce * Removes Parsetime Static Type Setting (#791) * Refactors statically set type to use a function * Lint fixes * Removed symbolTable getter from function scope - not used * Adds ReferenceType (#792) * Added ReferenceType and some tests * Added scope linking for hovers * Removed catch in try/finally in hoverprocessor * Reverted typo * Removed commented out code * Changed to using Proxy, fixed circular references * Fixed lint, added proper isReferencetype reflection * Added members to `BscType`, and adds ReferenceType (future lookups) (#794) * Large update to move BscType to a class and add ReferenceTypes * Fixes validation errors when calling a dynamicType like a function * Correctly resolves return types after a function call. * Removed some commented out or dead code * Fixed build errors * BscType.symbolTable -> memberTable, PropertyReferenceType->TypePropertyReferenceType * Fixed test that should fail * Fix enum transpile crash * Refactored `getType` to correctly use `SymbolTypeFlags` (#798) * Fixed issues with namespaces and invalid things being used as types * Removed unneeded method * Ran npm install with latest code * Removed commented and vestigal code, and added TODOs * Removed additional commented code * ClassType and InterfaceType refactor and Inheritance (#800) * Changed CustomType to ClassType * Adds BscType.equals(), and ClassType tests for inheritance * Fixes issue when looking for members of classes with supertypes that were originally ReferenceTypes * Rerfactor of InterfaceStatement and InetrfaceType work more similar to Classes * Added tests, changed parentClass/Interface to use TypeExpression * Fixes issue when classes and namespaces extend from a namespaced parent * Changes from code review * Type chain refactor (#802) * Changed getType() methods to use an options parameter * Refactors getType() to add opttions, and can find all the types in a chain of types * Fixed lint errors * Updated Tests * Update src/interfaces.ts Co-authored-by: Bronley Plumb * name change --------- Co-authored-by: Bronley Plumb * Adds Union types (#803) * initial commit * Adds UnionType and associated tests * Added helper functions to get the most general type from a list of types, including Union types * Started working on Uniontype.membersTypes * Started working on Uniontype.membersTypes * Refactor of types to use instead of * Fixed lint errors * Reverted benchmark package changes * Removes `NamespacedVariableNamedExpressions` (#801) * Removes NamespacedVariabledNamedExpressions * Fixed rest of tests * Fixes ignored parameter value in Parser.identifyingExpression * UnionType Improvements (#806) * Reworked type uniqness checking * Changed logic of InterfaceType.isEqual * Able to get member types from a UnionType * Fixed lint errors * Starting to add UnionExpression and parsing * Added pipe token * Added test for if inner type does not include member in UnionType.getMmeberTypes * Updated UnionExpression transpile logic * Re-added whitespace * removed pipe in favor of 'or' for declaring/displaying union types * Fix lint/build issues * TypeExpressions more narrowly look for what they can accept * Whitespace and typo fixes * Adds unionType validation tests and allows uniontypes innertypes to be late defined * fixed lint error * Fixes issue with ReferenceTypes not displaying a full name * Fix self references in unions (#808) * Fixes issue with self-referencing symbols in unions * Fixed lint error * Type performance improvements (#811) * Fixes issue with self-referencing symbols in unions * Fixed lint error * Starting performance benchmarking * Starting performance benchmarking * getType -> ZgetType() * Reverse getType removal * Removes double validation in ScopeValidator * Trying out type caching * Some benchmark thing * Removes TypeCache * Made the in typeExpression function more robust * Removed unneeded code * removed bad imports * Fixes issue with potential crash when checking types * Adds `enableTypeValidation` flag and documentation (#812) * Added bsConfig option for enhancedTypingValidation * Reverted any benchmark changes * Added docs for 'enhancedTypingValidation' option * Adds documentation * In an effort to increase non-enhancedtypeValidation, old methods were re-added to short circuit getType() calls * Changed flag to enableTypeValidation * Fixed docs * Fixed readme formatting * Fixes from PR review * Adds SymbolTable.getSymbolType() to wrap caching and ReferenceType creation (#813) * Adds SymbolTable.getSymbolType() to wrap caching and ReferenceType creation * Removed commented out code * Add SymbolTable exports for plugins, etc * Added exports from ./types * Adds Type Casting (#814) * Adds TypeCastExpression * Adds TypeCast expression * fix lint issue --------- Co-authored-by: Bronley Plumb --------- Co-authored-by: Mark Pearce Co-authored-by: Mark Pearce --- README.md | 6 + docs/readme.md | 7 + docs/union-types.md | 64 ++ package-lock.json | 34 +- package.json | 1 + src/BsConfig.ts | 6 + src/CacheVerifier.ts | 25 + src/DiagnosticMessages.ts | 11 +- src/FunctionScope.ts | 3 + src/Program.ts | 50 +- src/Scope.spec.ts | 898 +++++++++++++++++- src/Scope.ts | 44 +- src/SymbolTable.spec.ts | 59 +- src/SymbolTable.ts | 217 ++++- src/astUtils/reflection.spec.ts | 13 +- src/astUtils/reflection.ts | 60 +- src/astUtils/visitors.spec.ts | 6 +- src/astUtils/visitors.ts | 3 +- src/bscPlugin/hover/HoverProcessor.spec.ts | 106 ++- src/bscPlugin/hover/HoverProcessor.ts | 143 +-- .../BrsFileSemanticTokensProcessor.ts | 13 +- .../transpile/BrsFilePreTranspileProcessor.ts | 2 +- .../validation/BrsFileValidator.spec.ts | 2 +- src/bscPlugin/validation/BrsFileValidator.ts | 111 ++- src/bscPlugin/validation/ScopeValidator.ts | 117 ++- src/files/BrsFile.Class.spec.ts | 23 +- src/files/BrsFile.spec.ts | 74 +- src/files/BrsFile.ts | 118 ++- src/globalCallables.ts | 3 +- src/index.ts | 2 + src/interfaces.ts | 25 +- src/lexer/TokenKind.ts | 6 + src/parser/AstNode.ts | 11 +- src/parser/Expression.ts | 325 +++++-- src/parser/Parser.Class.spec.ts | 17 +- src/parser/Parser.spec.ts | 129 ++- src/parser/Parser.ts | 247 ++--- src/parser/Statement.ts | 206 ++-- .../tests/expression/TypeExpression.spec.ts | 127 +++ src/testHelpers.spec.ts | 13 +- src/types/ArrayType.spec.ts | 16 +- src/types/ArrayType.ts | 56 +- src/types/BooleanType.spec.ts | 13 +- src/types/BooleanType.ts | 26 +- src/types/BscType.ts | 84 +- src/types/ClassType.spec.ts | 86 ++ src/types/ClassType.ts | 25 + src/types/CustomType.ts | 31 - src/types/DoubleType.spec.ts | 16 +- src/types/DoubleType.ts | 43 +- src/types/DynamicType.spec.ts | 20 +- src/types/DynamicType.ts | 25 +- src/types/EnumType.spec.ts | 35 + src/types/EnumType.ts | 70 ++ src/types/FloatType.spec.ts | 6 +- src/types/FloatType.ts | 41 +- src/types/FunctionType.spec.ts | 10 +- src/types/FunctionType.ts | 49 +- src/types/InheritableType.ts | 92 ++ src/types/IntegerType.spec.ts | 14 +- src/types/IntegerType.ts | 42 +- src/types/InterfaceType.spec.ts | 117 ++- src/types/InterfaceType.ts | 78 +- src/types/InvalidType.spec.ts | 11 +- src/types/InvalidType.ts | 24 +- src/types/LongIntegerType.spec.ts | 14 +- src/types/LongIntegerType.ts | 42 +- src/types/NameSpaceType.ts | 24 + src/types/ObjectType.spec.ts | 4 +- src/types/ObjectType.ts | 38 +- src/types/ReferenceType.spec.ts | 145 +++ src/types/ReferenceType.ts | 280 ++++++ src/types/StringType.spec.ts | 4 +- src/types/StringType.ts | 29 +- src/types/UninitializedType.ts | 16 +- src/types/UnionType.spec.ts | 122 +++ src/types/UnionType.ts | 97 ++ src/types/VoidType.spec.ts | 4 +- src/types/VoidType.ts | 26 +- src/types/helper.spec.ts | 153 +++ src/types/helpers.ts | 132 +++ src/types/index.ts | 22 + src/util.spec.ts | 21 + src/util.ts | 83 +- src/validators/ClassValidator.ts | 47 +- 85 files changed, 4591 insertions(+), 1069 deletions(-) create mode 100644 docs/union-types.md create mode 100644 src/CacheVerifier.ts create mode 100644 src/parser/tests/expression/TypeExpression.spec.ts create mode 100644 src/types/ClassType.spec.ts create mode 100644 src/types/ClassType.ts delete mode 100644 src/types/CustomType.ts create mode 100644 src/types/EnumType.spec.ts create mode 100644 src/types/EnumType.ts create mode 100644 src/types/InheritableType.ts create mode 100644 src/types/NameSpaceType.ts create mode 100644 src/types/ReferenceType.spec.ts create mode 100644 src/types/ReferenceType.ts create mode 100644 src/types/UnionType.spec.ts create mode 100644 src/types/UnionType.ts create mode 100644 src/types/helper.spec.ts create mode 100644 src/types/helpers.ts create mode 100644 src/types/index.ts diff --git a/README.md b/README.md index 971ed7e35..4a724356b 100644 --- a/README.md +++ b/README.md @@ -542,6 +542,12 @@ Type: `boolean` Allow BrighterScript features (classes, interfaces, etc...) to be included in BrightScript (`.brs`) files, and force those files to be transpiled. +#### `enableTypeValidation` + +Type: `boolean` + +Performs additional validation on all declared and inferred types, such as checking member accesses in Classes and Interfaces, verifying that Namespace members exist when accessed, etc. + ## Ignore errors and warnings on a per-line basis In addition to disabling an entire class of errors in `bsconfig.json` by using `ignoreErrorCodes`, you may also disable errors for a subset of the complier rules within a file with the following comment flags: - `bs:disable-next-line` diff --git a/docs/readme.md b/docs/readme.md index 6bb8e9961..44a410938 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -108,3 +108,10 @@ second line text` ```brighterscript authStatus = user <> invalid ? "logged in" : "not logged in" ``` + +## [Union Types](union-types.md) +```brighterscript +sub log(data as string or number) + print data.toStr() +end sub +``` \ No newline at end of file diff --git a/docs/union-types.md b/docs/union-types.md new file mode 100644 index 000000000..8d2845297 --- /dev/null +++ b/docs/union-types.md @@ -0,0 +1,64 @@ +# Union Types + +BrighterScript Union Types are a way to define a type of a variable that could be any of a set of types. They are similar to Union Types found in other languages, such as [TypeScript](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types). + +## Syntax + +Union types can be declared with the following syntax: ` or `. For example, the parameter to the function below could be either an `integer` or a `string`: + +```BrighterScript +function makePriceString(value as string or integer) + return "$" + value.toStr() +end function +``` + +Any number of inner types, including classes or interfaces, could be part of a union: + +```BrighterScript +interface DataResponse + status as number + data as string +end interface + +interface ValueResponse + status as number + data as number +end interface + +interface Error + status as number + data as string +end interface + + +function getStatusString(response as DataResponse or ValueResponse or Error) as string + if response.status >= 200 and response.status < 300 + return "Success" + end if + return "Error" +end function +``` + +## Validation + +When the `enhancedTypeValidation` option flag is enabled, a diagnostic error will be raised when a member is accessed that is not a member of ALL of the types of a union. + +In the example above, accessing `response.status` is fine, because it is in all of the types of the union. Accessing `response.data` would raise an error, because it's not in the `Error` interface. + +## Transpilation + +Since Brightscript does not have union types natively, union types will be transpiled as `dynamic`. + +```BrighterScript +function makePriceString(value as string or integer) + return "$" + value.toStr() +end function +``` + +transpiles to + +```BrightScript +function makePriceString(value as dynamic) + return "$" + value.toStr() +end function +``` diff --git a/package-lock.json b/package-lock.json index 754ad52b5..d91c284ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "roku-deploy": "^3.10.2", "serialize-error": "^7.0.1", "source-map": "^0.7.4", + "uuid": "^9.0.0", "vscode-languageserver": "7.0.0", "vscode-languageserver-protocol": "3.16.0", "vscode-languageserver-textdocument": "^1.0.1", @@ -3946,6 +3947,16 @@ "semver": "bin/semver.js" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -6854,13 +6865,11 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { - "uuid": "bin/uuid" + "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache": { @@ -10082,6 +10091,12 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -12324,10 +12339,9 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" }, "v8-compile-cache": { "version": "2.3.0", diff --git a/package.json b/package.json index 2493e5415..83411b5c9 100644 --- a/package.json +++ b/package.json @@ -147,6 +147,7 @@ "roku-deploy": "^3.10.2", "serialize-error": "^7.0.1", "source-map": "^0.7.4", + "uuid": "^9.0.0", "vscode-languageserver": "7.0.0", "vscode-languageserver-protocol": "3.16.0", "vscode-languageserver-textdocument": "^1.0.1", diff --git a/src/BsConfig.ts b/src/BsConfig.ts index 40fd49cc9..67e61686e 100644 --- a/src/BsConfig.ts +++ b/src/BsConfig.ts @@ -182,4 +182,10 @@ export interface BsConfig { * @default false */ allowBrighterScriptInBrightScript?: boolean; + + /** + * Do full type checking & validation based on declared and inferred types. + * @default false + */ + enableTypeValidation?: boolean; } diff --git a/src/CacheVerifier.ts b/src/CacheVerifier.ts new file mode 100644 index 000000000..54e0722fc --- /dev/null +++ b/src/CacheVerifier.ts @@ -0,0 +1,25 @@ +import { v4 as uuid } from 'uuid'; + +export class CacheVerifier { + + private currentToken: string; + + constructor() { + this.generateToken(); + } + + generateToken() { + this.currentToken = uuid(); + } + + getToken(): string { + return this.currentToken; + } + + checkToken(token: string): boolean { + return token === this.currentToken; + } +} + + +export type CacheVerifierProvider = () => CacheVerifier; diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts index ceda048c4..3db691f46 100644 --- a/src/DiagnosticMessages.ts +++ b/src/DiagnosticMessages.ts @@ -206,7 +206,7 @@ export let DiagnosticMessages = { code: 1036, severity: DiagnosticSeverity.Error }), - invalidFunctionReturnType: (typeText: string) => ({ + __unused: (typeText: string) => ({ message: `Function return type '${typeText}' is invalid`, code: 1037, severity: DiagnosticSeverity.Error @@ -241,7 +241,7 @@ export let DiagnosticMessages = { code: 1043, severity: DiagnosticSeverity.Error }), - functionParameterTypeIsInvalid: (parameterName: string, typeText: string) => ({ + __unused2: (parameterName: string, typeText: string) => ({ message: `Function parameter '${parameterName}' is of invalid type '${typeText}'`, code: 1044, severity: DiagnosticSeverity.Error @@ -637,7 +637,7 @@ export let DiagnosticMessages = { code: 1122, severity: DiagnosticSeverity.Error }), - cannotFindType: (typeName: string) => ({ + __unused3: (typeName: string) => ({ message: `Cannot find type with name '${typeName}'`, code: 1123, severity: DiagnosticSeverity.Error @@ -724,6 +724,11 @@ export let DiagnosticMessages = { message: `Optional chaining may not be used in the left-hand side of an assignment`, code: 1139, severity: DiagnosticSeverity.Error + }), + itemCannotBeUsedAsType: (typeText: string) => ({ + message: `'${typeText}' cannot be used as a type`, + code: 1140, + severity: DiagnosticSeverity.Error }) }; diff --git a/src/FunctionScope.ts b/src/FunctionScope.ts index b825db4c6..c546bea03 100644 --- a/src/FunctionScope.ts +++ b/src/FunctionScope.ts @@ -51,4 +51,7 @@ export class FunctionScope { } } + get symbolTable() { + return this.func.body.getSymbolTable(); + } } diff --git a/src/Program.ts b/src/Program.ts index 256250ec7..c35a06129 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -15,7 +15,7 @@ import { DiagnosticFilterer } from './DiagnosticFilterer'; import { DependencyGraph } from './DependencyGraph'; import { Logger, LogLevel } from './Logger'; import chalk from 'chalk'; -import { globalFile } from './globalCallables'; +import { globalCallableMap, globalFile } from './globalCallables'; import { parseManifest } from './preprocessor/Manifest'; import { URI } from 'vscode-uri'; import PluginInterface from './PluginInterface'; @@ -29,6 +29,20 @@ import type { Statement } from './parser/AstNode'; import { CallExpressionInfo } from './bscPlugin/CallExpressionInfo'; import { SignatureHelpUtil } from './bscPlugin/SignatureHelpUtil'; import { DiagnosticSeverityAdjuster } from './DiagnosticSeverityAdjuster'; +import { IntegerType } from './types/IntegerType'; +import { StringType } from './types/StringType'; +import { SymbolTable, SymbolTypeFlags } from './SymbolTable'; +import { BooleanType } from './types/BooleanType'; +import { DoubleType } from './types/DoubleType'; +import { DynamicType } from './types/DynamicType'; +import { FloatType } from './types/FloatType'; +import { FunctionType } from './types/FunctionType'; +import { LongIntegerType } from './types/LongIntegerType'; +import { ObjectType } from './types/ObjectType'; +import { VoidType } from './types/VoidType'; +import { CacheVerifier } from './CacheVerifier'; +import { referenceTypeFactory } from './types/ReferenceType'; +import { unionTypeFactory } from './types/UnionType'; const startOfSourcePkgPath = `source${path.sep}`; const bslibNonAliasedRokuModulesPkgPath = s`source/roku_modules/rokucommunity_bslib/bslib.brs`; @@ -79,11 +93,16 @@ export class Program { public logger: Logger; + public typeCacheVerifier = new CacheVerifier(); + private createGlobalScope() { //create the 'global' scope this.globalScope = new Scope('global', this, 'scope:global'); this.globalScope.attachDependencyGraph(this.dependencyGraph); this.scopes.global = this.globalScope; + + this.populateGlobalSymbolTable(); + //hardcode the files list for global scope to only contain the global file this.globalScope.getAllFiles = () => [globalFile]; this.globalScope.validate(); @@ -91,6 +110,35 @@ export class Program { this.globalScope.getDiagnostics = () => []; //TODO we might need to fix this because the isValidated clears stuff now (this.globalScope as any).isValidated = true; + + // Adds a factory to SymbolTable so it can create ReferenceTypes + SymbolTable.ReferenceTypeFactory = referenceTypeFactory; + SymbolTable.cacheVerifier = this.typeCacheVerifier; + SymbolTable.UnionTypeFactory = unionTypeFactory; + } + + /** + * Do all setup required for the global symbol table. + */ + private populateGlobalSymbolTable() { + //Setup primitive types in global symbolTable + //TODO: Need to handle Array types + + this.globalScope.symbolTable.addSymbol('boolean', undefined, BooleanType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('double', undefined, DoubleType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('dynamic', undefined, DynamicType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('float', undefined, FloatType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('function', undefined, new FunctionType(DynamicType.instance), SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('integer', undefined, IntegerType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('longinteger', undefined, LongIntegerType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('object', undefined, new ObjectType(), SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('string', undefined, StringType.instance, SymbolTypeFlags.typetime); + this.globalScope.symbolTable.addSymbol('void', undefined, VoidType.instance, SymbolTypeFlags.typetime); + + for (let pair of globalCallableMap) { + let [key, callable] = pair; + this.globalScope.symbolTable.addSymbol(key, undefined, callable.type, SymbolTypeFlags.runtime); + } } /** diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts index fb7a915de..696a05c00 100644 --- a/src/Scope.spec.ts +++ b/src/Scope.spec.ts @@ -6,11 +6,20 @@ import { DiagnosticMessages } from './DiagnosticMessages'; import { Program } from './Program'; import { ParseMode } from './parser/Parser'; import PluginInterface from './PluginInterface'; -import { expectDiagnostics, expectZeroDiagnostics, trim } from './testHelpers.spec'; +import { expectDiagnostics, expectTypeToBe, expectZeroDiagnostics, trim } from './testHelpers.spec'; import { Logger } from './Logger'; import type { BrsFile } from './files/BrsFile'; import type { FunctionStatement, NamespaceStatement } from './parser/Statement'; import type { OnScopeValidateEvent } from './interfaces'; +import { SymbolTypeFlags } from './SymbolTable'; +import { EnumMemberType } from './types/EnumType'; +import { ClassType } from './types/ClassType'; +import { BooleanType } from './types/BooleanType'; +import { StringType } from './types/StringType'; +import { IntegerType } from './types/IntegerType'; +import { DynamicType } from './types/DynamicType'; +import { ObjectType } from './types/ObjectType'; +import { FloatType } from './types/FloatType'; describe('Scope', () => { let sinon = sinonImport.createSandbox(); @@ -72,13 +81,16 @@ describe('Scope', () => { const symbolTable = file.parser.references.namespaceStatements[1].body.getSymbolTable(); //the symbol table should contain the relative names for all items in this namespace across the entire scope expect( - symbolTable.hasSymbol('Beta') + // eslint-disable-next-line no-bitwise + symbolTable.hasSymbol('Beta', SymbolTypeFlags.runtime | SymbolTypeFlags.typetime) ).to.be.true; expect( - symbolTable.hasSymbol('Charlie') + // eslint-disable-next-line no-bitwise + symbolTable.hasSymbol('Charlie', SymbolTypeFlags.runtime | SymbolTypeFlags.typetime) ).to.be.true; expect( - symbolTable.hasSymbol('createBeta') + // eslint-disable-next-line no-bitwise + symbolTable.hasSymbol('createBeta', SymbolTypeFlags.runtime) ).to.be.true; expectZeroDiagnostics(program); @@ -496,8 +508,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [{ - ...DiagnosticMessages.cannotFindName('subname', 'Name1.subname'), - range: util.createRange(2, 26, 2, 33) + ...DiagnosticMessages.cannotFindName('subname', 'Name1.subname') }]); }); @@ -1139,6 +1150,29 @@ describe('Scope', () => { expect(plugin.afterScopeValidate.calledWith(compScope)).to.be.true; }); + it('supports parameter types in functions in AA literals defined in other scope', () => { + program.setFile('source/util.brs', ` + function getObj() as object + aa = { + name: "test" + addInts: function(a = 1 as integer, b =-1 as integer) as integer + return a + b + end function + } + end function + `); + program.setFile('components/comp.xml', trim` + + + \n \n \n \n\t\t\n\t\t\n\t\t\n\n\t\t\t\n\n \n \n \n\t\t\n\t\n\t\n\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\n","https://developer.roku.com/api/v1/get-dev-cms-doc?locale=en-us&filePath=developer-program/getting-started/architecture/content-metadata.md":"{\n \"content\": \"

Content metadata

\\n

Content metadata describes a viewable title that will be shown to the\\nuser. Content may be any supported type of video and the metadata is\\nused by the UI to format and display the title to the user. Some\\nattributes (e.g. ContentType) affect how the title is displayed on\\nscreen, other attributes (e.g. SDPosterURL) specify where to fetch\\nartwork to display with the content and other attributes (e.g. Title)\\nare just rendered as text.

\\n

Overview

\\n

The content metadata is stored in an associative array by the script\\nand provided to the various screen objects as needed for display. In\\nsome cases an array of content metadata may be provided so that the\\nscreen can render multiple items as a list. The attribute names are\\ncritical and used as the key to look up the attribute at run time. The\\nfollowing table details the attributes in use. Certain attributes are\\nrecognized by particular screens, while others are more globally\\napplicable. If the attribute is not a generally recognized attribute,\\nthe table below specifies where the attributes are recognized.

\\n

Keep in mind that there are two ways to specify stream content metadata,\\ndata.Stream and data.Streams:

\\n
    \\n
  • data.Stream: This is used when there is one stream URL,\\ntypically an HLS or smooth streaming manifest URL.
  • \\n
  • data.Streams: This is used when you have a set of fixed bitrate\\nstreams. This is typically the case for non-adaptive MP4 streams,\\nin which case multiple variants are specified to simulate true\\nadaptation.
  • \\n
\\n

Descriptive attributes

\\n

Descriptive metadata attributes can be used to describe the content\\nitem to the user, in a user interface element that allows the user to\\nselect the item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributesTypeValuesExample
ContentTypeStringAlthough ContentType accepts type String, the return value is of type roInt. See table below. \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
Content TypeReturn Value
audio5
episode4
movie1
not set or not supported0
season3
series2
\\\"movie\\\"
TitleStringContent title: movie title for films; episode title for TV series\\\"The Dark Knight\\\"
TitleSeasonStringSeason title for TV series\\\"Battlestar Galactica Season 5\\\"
SecondaryTitle

Available since Roku OS 11.5
StringSecondary title for the video content\\\"2022\\\" (release year of the movie)
DescriptionStringDescription of content\\\"Batman, Gordon and Harvey Dent are forced…\\\"
SDPosterUrlStringURL for SD content artworkmysite.com/img/sd1932.jpg
HDPosterUrlStringURL for HD content artworkmysite.com/img/hd1932.jpg
FHDPosterUrlStringURL for FHD content artworkmysite.com/img/fhd1932.jpg
ReleaseDateStringFormatted Date String\\\"3/31/2009\\\"
RatingStringSelects an icon to be displayed for the corresponding MPAA or TV rating, that is, the value will display as an icon artwork. See Rating Attribute Icons for a list of the acceptable values and the corresponding icon.\\\"PG-13\\\"
StarRatingIntegerSpecifies the star rating to display as red star icon artwork, as a number from 1 to 100:
    \\n
  • 20 displays one star
  • \\n
  • 40 displays two stars
  • \\n
  • 60 displays three stars
  • \\n
  • 80 displays four stars
  • \\n
  • 100 displays five stars
  • \\n
Numbers not divisible by 20 are displayed as a fractional star (A number of 30 will display one and a half stars)
80
UserStarRatingIntegerSpecifies the user star rating to display as yellow star icon artwork, as a number from 1 to 100:
    \\n
  • 20 displays one star
  • \\n
  • 40 displays two stars
  • \\n
  • 60 displays three stars
  • \\n
  • 80 displays four stars
  • \\n
  • 100 displays five stars
  • \\n
Does not display fractional stars for numbers not divisible by 20
80
ShortDescriptionLine1StringLine 1 of Poster Screen Description\\\"The Dark Knight\\\"
ShortDescriptionLine2StringLine 2 of Poster Screen Description\\\"Rent $1.99, Buy $14.99\\\"
EpisodeNumberStringEpisode Number\\\"1\\\"
NumEpisodesIntegerNumber of episodes for a \\\"season\\\" or \\\"series\\\" contentType40
ActorsroArrayList of Actor Names[\\\"Brad Pitt\\\", \\\"Angelina Jolie\\\"]
ActorsStringIndividual Actor Name\\\"Brad Pitt\\\"
DirectorsroArrayList of Director Names[\\\"Joel Coen\\\", \\\"Clint Eastwood\\\"]
CategoriesroArrayList of Category/Genre Names[\\\"Comedy\\\", \\\"Drama\\\"]
CategoriesStringIndividual Category/Genre Name\\\"Comedy\\\"
AlbumStringroSpringboard audio style uses this to display the album\\\"Achtung\\\"
ArtistStringroSpringboard audio style uses to show artist\\\"U2\\\"
TextOverlayULStringroSlideShow displays this string in Upper Left corner of slide\\\"Joe's Photos\\\"
TextOverlayURStringroSlideShow displays this string in Upper Right corner of slide\\\"3 of 20\\\"
TextOverlayBodyStringroSlideShow displays this string on the bottom part of slide\\\"Wanda's 40'th Birthday\\\"
\\n

Digital rights management (DRM) control attributes

\\n

Digital rights management (DRM) content meta-data control attributes are available in the Roku OS through the drmParams parameter of type roAssociativeArray. The table below enumerates all usable attributes of drmParams.

\\n

Note: Not all attributes are required, and may not have the same semantic meaning when applied to different DRM systems.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeDRM SystemTypeValueExample
appDataPlayready, Widevine, Verimatrix: OptionalStringSpecial meaning per DRM system. If supplied, expected to be a base64 encoded string.\\\"SGF2ZSB0byBkZWFsIHdpdGggQmFzZTY0IGZ...\\\"
encodingKeyPlayready: OptionalStringThis field is deprecated; use the licenseServerURL field.

Specifies the PlayReady license acquisition data, in format depending on the EncodingType attribute value specified:
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionUrl\\\", the EncodingKey attribute contains the PlayReady license acquisition URL
  • \\n
\\\"http://serverName/
encodingTypePlayready: OptionalStringThis field is deprecated; use the licenseServerURL field.

Specifies the encoding scheme for PlayReady DRM, by setting to one of the following values:
    \\n
  • \\\"PlayReadyLicenseAcquisitionUrl\\\"
  • \\n
  • \\\"PlayReadyLicenseAcquisitionAndChallenge\\\" Note, this is the same value that used to be specified directly in Content Metadata structure
  • \\n
\\\"PlayReadyLicenseAcquisitionAndChallenge\\\"
KeySystemRequired for allString\\\"playready\\\" or \\\"widevine\\\". This value is case-insensitive. The default is an empty string.

\\n

As of Roku OS 9.3, support for Verimatrix DRM has been removed from the firmware. Make sure that content in your channel is protected using one of the following Roku-supported DRMs: Microsoft PlayReady or Widevine. Click here for more information on implementing these DRMs.

\\n
\\\"widevine\\\"
licenseRenewURLWidevine: OptionalStringA URL location for sending license renewal requests. If not specified, the Roku OS would send renewal requests to the URL specified in the licenseServerURL.\\\" https://host.com/license/wideivne/renew?licenseid=090495867002 \\\"
licenseServerURLPlayready: Required Widevine: RequiredStringA URL location of a license server. This URL may include CGI parameters.

If this field contains the PlayReady license acquisition URL plus additional custom license acquisition request data in format \\\"URL%%%\\\", the “PlayReadyLicenseAcquisitionAndChallenge\\\" type is used.
\\\"https://host.com/license/playready?contentid=090495867002 \\\"
serializationURLPlayready, Widevine: OptionalStringA server address used for device provisioning\\\"https://host.com/provision/device?esn=090495867002 \\\"
serviceCertWidevine: Optional Others: N/R (leave unset)StringThe actual certificate string for Widevine purposes, which must be obtained out-of-band (OOB) by the channel. Leave this unset unless Widevine is used for DRM.Certificate strings are too long to display here. Examples can be fetched from such sources as the Widevine test license server at \\\"https://proxy.uat.widevine.com/proxy. \\\"
lic_acq_window

Available since Roku OS 10.5
Widevine: OptionalstringThe maximum amount of time (in milliseconds) that a channel waits before rotating its Widevine DRM keys. The channel can generate a random wait time between 0 and the value specified in the lic_acq_window field, and use the random wait time to instruct when the Video node should make its next Widevine license request.1000
\\n
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionAndChallenge\\\", the EncodingKey attribute contains the PlayReady license acquisition URL plus additional custom license acquisition request data in format \\\"URL%%%\\\" Note, this is the same value that used to be specified directly in Content Metadata structure The channel just needs to set drmParams.licenseSererUrl.
  • \\n
\\n

Passing custom HTTP headers to licensing requests

\\n

Developers looking to pass custom HTTP headers with a licensing request can now set those headers using the ifHttpAgent interface methods on the Video node.

\\n

Example of configuring a dash stream with Widevine DRM

\\n
contMeta = {\\n    HDPosterUrl:\\\"pkg:/images/BigBuckBunny.jpg\\\"\\n    SDPosterUrl:\\\"pkg:/images/BigBuckBunny.jpg\\\"\\n    ShortDescriptionLine1:\\\"Parking Wars(VOD)\\\"\\n    ShortDescriptionLine2:\\\"dash | widevine\\\"\\n    Streamformat:\\\"dash\\\"\\n    SwitchingStrategy:\\\"\\\"\\n    MinBandwidth:500\\n    VideoUrl: \\\"http://dev.domain.com/mm/dash/vod/173850/85768039/TG_W_WIFI.mpd\\\"\\n    drmParams: { ' setting up DRM config\\n        keySystem: \\\"Widevine\\\"\\n        licenseServerURL: \\\"http://msfrn-ci-cp-dev.mobitv.com/widevine/get_license\\\"\\n    }\\n}\\n
\\n

Content classification attributes

\\n

Available since Roku OS 13.0

\\n

Developers can use the contentClassifier content metadata attribute to specify the genre of their content (for example, action, sports, or comedy), and the Roku OS will use this attribute to automatically adjust the sound and picture on Roku TVs (if auto mode is selected for the picture or sound settings).

\\n
Content classifier value
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
contentClassiferstring
    \\n
  • \\\" \\\"
  • \\n
  • \\\"action\\\"
  • \\n
  • \\\"animated\\\"
  • \\n
  • \\\"black+white\\\" (black and white)
  • \\n
  • \\\"comedy\\\"
  • \\n
  • \\\"drama\\\"
  • \\n
  • \\\"music\\\"
  • \\n
  • \\\"music:lyrics\\\"
  • \\n
  • \\\"nature\\\"
  • \\n
  • \\\"news\\\"
  • \\n
  • \\\"podcast\\\" (audio only)
  • \\n
  • \\\"reality\\\"
  • \\n
  • \\\"sports\\\"
  • \\n
\\\"drama\\\"
\\n
Content classifier sound and picture modes
\\n

The following table details how the different contentClassifier attribute values are mapped to sound and picture modes on Roku TVs.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
Content ClassifierSound ModePicture Mode
\\\" \\\"StandardStandard
actionMovieMovie
sportsStandardSports
comedyMovieMovie
dramaMovieMovie
musicMusicStandard
music:lyricsMusicLow Power
newsDialogVivid
podcast (Audio Only )DialogLow Power
animatedMovieVivid
black+whiteMovieStandard
natureStandardVivid
realityStandardStandard
\\n

Playback configuration attributes

\\n

Playback configuration meta-data attributes are used to configure the playback of the content item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
LiveBooleanOptional flag indicating video is live. Replaces time remaining in progress bar to display \\\"Live\\\". Default is falseTrue
UrlStringStream URL for Scene Graph Video nodemysite.com/img/vacation.jpg
SDBifUrlStringBIF URL for SD trick modemysite.com/bif/sd1932.bif
HDBifUrlStringBIF URL for HD trick modemysite.com/bif/hd1932.bif
FHDBifUrlStringBIF URL for FHD trick modemysite.com/bif/fhd1932.bif
StreamroAssociativeArraySupported by roVideoPlayer and roVideoScreen, but not the Roku Scene Graph Video node.
For the Video node, use the top level url, streamformat, etc. attributes.

The exception is cases where you don't have adaptive streams (typically MP4) and need to specify different bitrate variants separately. For this use case use the Streams attribute. roAssociativeArray that has parameters representing the stream settings that were set as individual roArrays in previous firmware revisions.

The old method is still supported and descriptions of the parameters can be found under those content-meta data entries.

For url please see StreamUrls, for quality it is now a Boolean that is true for HD quality.
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyType
urlString
stickyredirectsBoolean
qualityBoolean
contentidString
bitrateInteger
{ url : \\\"http://me.com/big.m3u8\\\", quality : true, contentid : \\\"big-hls\\\" }
StreamsroArray of roAssociativeArraysUsed by roVideoPlayer and roVideoScreen to specify the content metadata for a set of fixed bitrate streams.

Each array item specifies the URL, bitrate, etc. for one stream variant.

Setting stream content metadata using the Streams value is recommended for non-adaptive video (such as MP4 progressive download) only.

For adaptive streaming, use the Stream metadata value.
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyType
urlString
stickyredirectsBoolean
qualityBoolean
contentidString
bitrateInteger
[ { url : \\\"http://me.com/x-384.mp4\\\", bitrate : 384, quality : false, contentid : \\\"x-384\\\" }, { url : \\\"http://me.com/x2500.mp4\\\", bitrate : 2500, quality : true, contentid : \\\"x-1500\\\" } ]
StreamBitratesroArrayArray of bitrates in kbps for content streams used.

Setting stream bitrates using this value is recommended for non-adaptive video (such as MP4 progressive download) only.

Must be used in conjunction with StreamUrls and StreamQualities
[ 384, 500, 1000, 1500 ]
StreamUrlsroArrayArray of URLs for content streams.

Setting stream urls using this value is recommended for non-adaptive video (such as MP4 progressive download) only.

Must be used in conjunction with StreamBitrates and StreamQualities
[ \\\"mysite.com/vid/1932-1.mp4\\\", \\\"mysite.com/vid/1932-2.mp4\\\", \\\"mysite.com/vid/1932-3.mp4\\\", \\\"mysite.com/vid/1932-4.mp4\\\" ]
StreamQualitiesroArrayArray of Strings quality indicators identifying a stream as \\\"SD\\\" or \\\"HD\\\".

Must be used in conjunction with StreamBitrates and StreamUrls
[ \\\"SD\\\", \\\"SD\\\", \\\"SD\\\", \\\"HD\\\" ]
StreamContentIDsroArrayarray of strings values logged in Roku logs to identify stream and bitrate played[ \\\"myco-19321-384.mp4\\\", \\\"myco-19321-500.mp4\\\", \\\"myco-19321-1000.mp4\\\", \\\"myco-19321-1500.mp4\\\" ]
StreamStickyHttpRedirectsroArrayArray of Boolean values indicating if the HTTP endpoint should be sticky and not subject to change on subsequent requests. Default is false[ false, false, false, false ]
StreamStartTimeOffsetIntegerOptional. Default is 0. The offset into the stream which is considered the beginning of playback. Time in seconds.3600 (one hour)
StreamFormatStringType of content
    \\n
  • \\n

    Type of content:

    \\n
      \\n
    • Default: H.264/AAC in .mp4 Container
    • \\n
    \\n
  • \\n
  • \\n

    Valid values:

    \\n
      \\n
    • \\\"mp4\\\" (mp4 will also accept .mov and .m4v files)
    • \\n
    • \\\"wma\\\" (deprecated)
    • \\n
    • \\\"mp3\\\"
    • \\n
    • \\\"hls\\\"\\n-\\\"ism\\\" (smooth streaming)
    • \\n
    • \\\"dash\\\" (MPEG-DASH)
    • \\n
    • \\\"mkv\\\", \\\"mka\\\", \\\"mks\\\"
    • \\n
    \\n
  • \\n
  • \\n

    Deprecated:

    \\n
      \\n
    • \\\"wmv\\\"
    • \\n
    \\n
  • \\n
LengthFloatMovie Length in Seconds; Length zero displays at 0m, Length not set will not display3600 (one hour)
PlayStartFloatPlayStart defines the start position of the content, in seconds.

The player is not allowed to move to a position prior to this point. Any seek operation prior to this point will be clipped to PlayStart.

Channels can use PlayStart and PlayDuration to split one content piece into multiple clips and insert these clips with other content (typically advertisements) into one content list.

Starting from Roku OS 8.0, content metadata supports negative PlayStart values. This feature allows the media players to start playbacks distanced from the edge of the live stream
0
ClosedCaptionsBooleanBoolean indicating if CC icon should be displayedTrue
HDBrandedBooleanBoolean indicating if HD branding should be displayedTrue
IsHDBooleanBoolean indicating if content is HDTrue
SubtitleColorStringTheme metadata attribute that specifies the color to use when rendering subtitle text\\\"#FF0000\\\"
SubtitleConfigroAssociativeArray: {TrackName : String}Specifies the caption settings for content playback.

TrackName sets the name of the caption track to render. This string is a concatenation of the track source and track id, separated by a \\\"/\\\".

Valid track sources are: \\\"ism\\\", \\\"mkv\\\", \\\"eia608\\\" and \\\"dvb\\\".

The track id must match the track identifier in the smooth or mkvmanifest. For example, if an mkvfile has a caption track called “english1” the TrackName to select this track is “mkv/english1”.

When the track source is \\\"dvb\\\", the track id is the three-letter language code, with \\\"_sdh\\\" appended for subtitles for the deaf and hard of hearing. For example, \\\"dvb/eng_sdh\\\" are English subtitles for the deaf and hard of hearing and \\\"dvb/nor\\\" are normal Norwegian subtitles.

For sideloaded caption tracks, the TrackName is the url from where the caption track can be downloaded.Sideloaded caption formats can include srt,ttml, anddfxp. Specifying eia608/1 will trigger the Roku OS to search for embedded 608/708 captions in the stream. In the 8.0 Roku OS, automatic track selection based on a preferred caption language setting is introduced. Omit setting a URL here to avoid interfering with the automatic track selection. It is sufficient to add the URLs to SubtitleTracks
{ TrackName : \\\"mkv/english1\\\" }
SubtitleTracksroArray of roAssociativeArray: [{Language : String, Description : String, TrackName : String},...]SubtitleTracks sets the list of available caption tracks available to the stream. This list is added to the track list in the closed caption configuration dialog that is displayed during video playback when the user presses the Roku remote control * button. The captions from the selected track are then displayed on the screen. Language specifies the ISO 639.2B 3 character language code. This string is used to match the proper caption track with the audio language. Description specifies the text that will be shown for the corresponding track in the closed caption configuration dialog. For sideloaded caption tracks the TrackName is the URL from where the caption track can be downloaded. Sideloaded caption formats can include srt, ttml, and dfxp. The SubtitleTracks metadata is generally only used for side loaded captions. the Roku OS detects in-stream captions and thus specifying SubtitleTracks in this case is not necessary
SubtitleUrlStringSpecifies the path to an SRT or TTML formatted file used to render subtitles or closed captions, respectively. This is supported on roVideoScreen only. See Closed Caption Support for additional details\\\"mysite.com/vid/1932.srt\\\"; \\\"mysite.com/vid/1932.xml\\\"
VideoDisableUIBooleanIf set to true, hides the Scene Graph Video node trick play UI; If set to false (the default) shows the Scene Graph Video node trick play UIvideo = createObject(\\\"roSGNode\\\", \\\"Video\\\"); video.content.VideoDisableUI = true
EncodingTypeStringSpecifies the encoding scheme for PlayReady DRM, by setting to one of the following values:
    \\n
  • \\\"PlayReadyLicenseAcquisitionUrl\\\"
  • \\n
  • \\\"PlayReadyLicenseAcquisitionAndChallenge\\\" Note, this is the same value that used to be specified directly in Content Metadata structure
  • \\n
EncodingKeyStringSpecifies the PlayReady license acquisition URL, and additional custom request data, determined by the EncodingType attribute value specified:
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionUrl\\\", the EncodingKey attribute contains the PlayReady license acquisition URL
  • \\n
SwitchingStrategyStringroVideoPlayer or roVideoScreen.

Specify different stream switching algorithms to be used in HLS adaptive streaming.
Only has an effect on HLS streams. \\\"full-adaptation\\\" uses measured bandwidth and buffer fullness to determine when to switch. This strategy requires that segments align across variants as the HLS spec requires. This is the new default
\\\"full-adaptation\\\"
WatchedBooleanFlag indicating if content has been watchedTrue
ForwardQueryStringParamsBooleanControls whether query string parameters from initial HLS stream manifest requests are forward to subsequent segment download requests. The default value is set to true for backwards compatibility.True
ForwardDashQueryStringParamsBooleanControls whether query string parameters from initial DASH stream manifest requests are forward to subsequent segment download requests. The default value is set to false for backwards compatibility.False
IgnoreStreamErrorsBooleanWhen set to true the media player will not stop playback when it runs into a streaming related error for this content. Instead, it will skip to the next item in the content list.

If this was the last item in the content list the media player will send a regular completion event (like isFullResult). Channels are still notified of any errors via an isRequestFailed notification but a new attribute in the event’s GetInfo object tells the channel the error was ignored.

See the changes related to isRequestFailed for more information. The default value is false.
video_details = {\\n    streamFormat: \\\"mp4\\\"\\n    ignoreStreamErrors: true\\n    streams: [{bitrate: 537, height: 360, width: 640, url: “https://...\\\"}]\\n}
AdaptiveMinStartBitrateIntegerMinimum startup bitrate specified in kbps. Streaming will start with a variant equal to or greater than this value. If this value is not set or if it's set to zero, any minimum start bitrate will be ignored.5000
AdaptiveMaxStartBitrateIntegerMaximum startup bitrate specified in kbps. Streaming will start with a variant less than or equal to this value. If this value is not set, it will default to 2500 kbps.2000
filterCodecProfilesBooleanFilters out any video profile/codec level combinations that the specified hardware cannot play. The default value is false, in which case no filtering occurs. Note that this currently only works for DASH streams.True
LiveBoundsPauseBehaviorStringAllows a channel to customize Media Player behavior on live streams when playing in the earliest part of a DVR buffer.

The stream remains paused even though it is playing in the earliest part of the buffer of a live stream when the value of the attribute is set to \\\"pause.\\\" This enables the Roku OS to distinguish between live streams and live streams that eventually transition to video on demand.

The possible values of this attribute are \\\"resume\\\", \\\"stop\\\", \\\"pause\\\", with resume being the default value.

Currently, this attribute will work only with Smooth and Dash streams. (Available since Roku OS 8.1)
Resume
ClipStartFloatClipStart sets the clip start position of the playback. The unit of ClipStart is seconds (Available since Roku OS 8.1).00.0
ClipEndFloatClipEnd sets the clip end position. The unit of ClipEnd is seconds (Available since Roku OS 8.1).00.0
PreferredAudioCodecStringSpecifies the audio codec that should be used during playback. The Media Player will select and report to the channel only those audio renditions that are encoded with the specified codec. Renditions that are encoded with a different codec are ignored. Possible values of this attribute are \\\"aac\\\", \\\"ac3\\\" and \\\"eac3\\\". (Available since Roku OS 9.0)\\\"aac\\\"
AudioWhitelistStringComma-separated list of audio tracks (based on ISO 639-1 or ISO 639-2 language code) that may be selected from the Audio track setting for the content.

\\\"en, spa\\\"
AudioBlacklistStringComma-separated list of audio tracks (based on ISO 639-1 or 639-2 language code) that may not be selected from the Audio track setting for the content.

(Available since Roku OS 9.4)

If a language is both blacklisted and whitelisted, the blacklisting takes precedence.
\\\"ita, fr\\\"
CaptionWhitelistStringComma-separated list of captioning tracks (based on ISO 639-2 language code) that may be selected from the Accessibility>Captioning track setting for the content.

\\\"en, spa\\\"
CaptionBlacklistStringComma-separated list of captioning tracks (based on ISO 639-2 language code) that may not be selected from the Accessibility>Captioning track setting for the content.

(Available since Roku OS 9.4)

If a language is both blacklisted and whitelisted, the blacklisting takes precedence.
\\\"deu, dan\\\"
\\n

CDN switching

\\n

Content Delivery Networks (CDNs) can be switched during playback to load balance traffic and failover to different servers in order to help optimize performance. The CdnConfig attribute can be used for managing load balancing and failovers.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesDescription
cdnConfigroArray of roAssociativeArrays\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyRequired/ OptionalTypeDescription
URLFilterRequiredStringA substring that identifies the (base)URL to which these CDN settings apply.

The Roku media player matches this string against all (base)URLs listed in the manifest and applies the setting to all (base)URLs that contain this substring.
ContentFilterOptionalStringFor DASH streams, a substring that filters the period or asset ID to which these CDN settings apply.

The Roku player only applies these CDN setting to periods with a period ID or asset ID that contains this substring.

This match is used in addition to the URL filter.
PriorityRequiredIntegerFor configuring failovers, sets the priority for this (base)URL from 1 to x (a priority of 0 or less is invalid).

A lower value indicates a higher priority. For example, a (base)URL with a priority of 1 is higher than another with a priority of 10.

If the highest priority server fails, traffic is routed to the server with the next highest priority. If all servers are configured with the same priority, and one fails, no failover will happen.
WeightRequiredIntegerFor configuring load balancing, sets the relative weight for all (base)URLs with the same priority. This must be a value of 1 or greater (a weight of 0 disables a CDN).

The weight of a given BaseURL is its weight value divided by the sum of all weight values. This means that to spread the load equally across multiple CDNs with the same priority, set the weight for each to the same value. To configure the weights for two servers to 80% and a third server to 20%, for example, set servers one and two to 8 and server three to 4.
ServiceLocationOptionalStringA blacklist of failed BaseURL locations.
To use this field, create a child node and use a playlist (even though only one content item will be in the playlist). This field is updated only when contentIsPlayList is true.

The URLFilter, Priority, and Weight attributes must be specified to apply these configurations.
\\n

Example

\\n
this.cur_clip.CDNConfig = [\\n    {URLFilter:\\\"http://cdn1.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 1, weight: 50, serviceLocation: \\\"west\\\"},\\n    {URLFilter:\\\"http://cdn2.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 1, weight: 50, serviceLocation: \\\"east\\\"},\\n    {URLFilter:\\\"http://cdn1.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 2, weight: 50, serviceLocation: \\\"west\\\"},\\n    {URLFilter:\\\"http://cdn2.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 2, weight: 50, serviceLocation: \\\"east\\\"},\\n]\\n
\\n

SceneGraph certificate attributes

\\n

The SceneGraph certificate meta-data attributes are used to configure\\nthe use of HTTP certificates and cookies by the Audio and Video nodes.\\nPlease note that when setting any of the following four attributes on\\na Video or Audio node, you need to be careful that the values are set on\\nthe correct HTTPAgent. If the node does not have its own HTTPAgent, set\\nby explicitly calling setHttpAgent() on the node, the Roku OS will\\ntraverse up the scene graph hierarchy until it finds the first node in\\nthe Video or Audio node's ancestry that has set an HTTPAgent. If none\\nis found, the values will be set on the global HTTPAgent which is always\\nguaranteed to exist. Therefore if you expect the header, etc. values\\nset to only apply to your Audio and Video nodes, create a unique\\ninstance of roHttpAgent for them and assign it directly. For example,\\nfor a Video node you should do the following:

\\n
'Assume video is a valid Video node instance\\n\\nhttpAgent = CreateObject(\\\"roHttpAgent\\\")\\nvideo.setHttpAgent(httpAgent)\\n
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValues
HttpCertificatesFileuriIf set, the Scene Graph Audio or Video node loads this public certificate bundle, to authenticate the server. The protocol must be https for this to have any effect. When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When playing this content, the agent is updated in the following manner:
    \\n
  • If this attribute is defined, the file URI is set into the HttpAgent instance. However, if this attribute is specified and the value is the empty string (\\\"\\\"), then no changes will be made to the HttpAgent.
  • \\n
  • \\n

    If this attribute is not defined, the behavior depends upon whether the Content Meta-Data (CMD) contains secure (https) URLs:

    \\n
      \\n
    • If no secure URLs exist in the meta-data, then no certificates file path is set into the agent.
    • \\n
    • If a secure URL does exist, the platform's default certificates are set into the agent.
    • \\n
    \\n
  • \\n
HttpCookiesarray of stringsIf set, the Scene Graph Audio or Video node send the cookies to the server. Each cookie must have the following syntax: dom=domain;path=path;name=name;val=value; When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute is set, all HTTP cookies in the agent are cleared and replaced with the cookies defined by this attribute
HttpHeadersarray of stringsIf set, the Scene Graph Audio or Video node sends these headers to the server. Each string must be of the format \\\"name:value\\\". When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute is set, all HTTP headers in the agent are cleared and replaced with the headers defined by this attribute
HttpSendClientCertificateBooleanIf true, the Scene Graph Audio or Video node sends the client device certificate to the server, for client authentication. The protocol must be https for this to have any effect. When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute exists, the value of this attribute (true or false) is set into the HttpAgent
\\n

drmHttpAgent for handling DRM key/license requests separately

\\n

Since Roku OS 9.3, you can create a separate agent to handle DRM key and license requests, apart from other types\\nof requests.

\\n

Once you have created your agent, you can set the Video node's drmHttpAgent field directly to designate that the special\\nagent is to supersede any currently-set agent in the case of DRM key and license requests. The drmHttpAgent field must be configured before setting the content in the Video node.

\\n
' Configure the DRM HttpAgent before setting content in the Video node\\n httpAgent = CreateObject(\\\"roHttpAgent\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-1\\\", \\\"weqweqweqweqweqweqeqeqeqeqwe\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-2\\\", \\\"fgfgfgfgfgfgfgfgfg\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-3\\\", \\\"zxzxzxzxxzxzxzxzxzx\\\")\\n m.video.drmHttpAgent = httpAgent    \\n m.video.content = videocontent\\n
\\n

If drmHttpAgent is not set (the default), uri fetches for video involving the DRM URLs\\n(serializationURL, licenseServerURL, licenseRenewURL) of ContentMetaData will\\nuse the video's regular HttpAgent. However, if the drmHttpAgent is set, the agent\\ncited in the field will be used for those fetches instead.

\\n
\\n

The \\\"SceneGraph Certificate Attributes\\\" mentioned above all have \\\"Drm\\\" versions,\\nwith names formed by the prefixing \\\"Drm\\\" to the \\\"regular\\\" names\\n(e.g., HttpCookies becomes DrmHttpCookies, and so forth).\\nThese attributes take precedence over those of the drmHttpAgent.

\\n
\\n

Playback control attributes

\\n

The playback control meta-data attributes are used to control\\nthe playback parameters for the content item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
MinBandwidthIntegerroVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth higher than this minimum bandwidth. Units are kbps. By default Wowza servers set streams to 64 kbs, so you might want to set this parameter to something smaller than 64 when first testing Wowza streams. You will eventually want to specify the Wowza bitrates with a smil file (Please see the encoding guide)48
MaxBandwidthIntegerroVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth less than this maximum bandwidth. Units are kbps2500
AudioPIDPrefIntegerThis attribute is deprecated

Users can select their preferred audio language on-device in the Settings > Audio > Audio Preferred Language screen.
483
FullHDBooleanroVideoPlayer or roVideoScreen: Specify that this stream was encoded at 1080p resolutiontrue
FrameRateIntegerroVideoPlayer or roVideoScreen: Specify the 1080p stream was encoded at 24 or 30 fps24
\\n

Track ID attributes

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
TrackIDAudioStringroVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \\\"ISM\\\") to specify. Set the TrackIDAudio field to the desired track's StreamIndex.Name attribute from the manifest file\\\"Spanish\\\"
TrackIDVideoStringroVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \\\"ISM\\\") to specify. Set the TrackIDVideo field to the desired track's StreamIndex.Name attribute from the manifest file\\\"h264video\\\"
TrackIDSubtitleStringroVideoPlayer: Used to specify a closed caption track in a video stream that supports 608/708 captions\\\"eia608/1\\\"
AudioFormatStringroSpringboardScreen: If set to \\\"dolby-digital\\\", will display a \\\"5.1 ))\\\" icon in the lower left of a movie style springboard screen\\\"dolby-digital\\\"
AudioLanguageSelectedStringThis attribute was deprecated as of the Roku 9.2 OS release.

Users can select their preferred audio language on-device in the Settings > Audio > Audio Preferred Language screen.
\\\"eng\\\"
\\n

roListScreen attributes

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
SDBackgroundImageUrlStringroListScreen: URL for the SD background imagemysite.com/images/bg1_sd.jpg
HDBackgroundImageUrlStringroListScreen: URL for the HD background imagemysite.com/images/bg1_hd.jpg
\\n

Rating attribute icons

\\n

The Rating attribute contains the MPAA or TV rating stored as a string.\\nAt runtime, the ratings are shown with an icon instead of rendering the\\nstring as text. The following table shows the list of valid values for\\nthe Rating attribute, and the resulting icon that will be displayed for\\neach value.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
ValueIcon
G\\\"roku815px
NC-17\\\"roku815px
PG\\\"roku815px
PG-13\\\"roku815px
R\\\"roku815px
UR\\\"roku815px
UNRATED\\\"roku815px
NR\\\"roku815px
TV-Y\\\"roku815px
TV-Y7\\\"roku815px
TV-Y7-FV\\\"roku815px
TV-G\\\"roku815px
TV-PG\\\"roku815px
TV-14\\\"roku815px
TV-MA\\\"roku815px
\\n

Content feed video lesson

\\n

You can learn how to link the content metadata in your channel's feed to a ContentNode by watching the Creating the content feed video lesson in Roku's SceneGraph: Build a Channel online video course.

\"\n}"} diff --git a/scripts/scrape-roku-docs.ts b/scripts/scrape-roku-docs.ts index 2f59e4246..4210520b1 100644 --- a/scripts/scrape-roku-docs.ts +++ b/scripts/scrape-roku-docs.ts @@ -17,7 +17,7 @@ import * as deepmerge from 'deepmerge'; import { NodeHtmlMarkdown } from 'node-html-markdown'; import { isVariableExpression } from '../src/astUtils/reflection'; import { SymbolTable } from '../src/SymbolTable'; -import { SymbolTypeFlag } from '../src/SymbolTableFlag'; +import { SymbolTypeFlag } from '../src/SymbolTypeFlag'; import { referenceTypeFactory } from '../src/types/ReferenceType'; import { unionTypeFactory } from '../src/types/UnionType'; @@ -52,7 +52,6 @@ class Runner { public async run() { const outPath = s`${__dirname}/../src/roku-types/data.json`; - fsExtra.removeSync(outPath); SymbolTable.referenceTypeFactory = referenceTypeFactory; SymbolTable.unionTypeFactory = unionTypeFactory; @@ -66,6 +65,9 @@ class Runner { await this.buildEvents(); await this.buildNodes(); + //ContentNode fields are a special case + await this.buildContentNodeFields(); + //include hand-written overrides that were missing from roku docs, or were wrong from roku docs. this.mergeOverrides(); @@ -375,7 +377,7 @@ class Runner { // make an array of at the words in each group, removing "as" if it exists const words = foundParam.split(' ').filter(word => word.length > 0 && word.toLowerCase() !== 'as'); // find the index of the word that looks like a type - const paramTypeIndex = words.findIndex(word => potentialTypes.includes(this.sanitizeMarkdownSymbol(word.toLowerCase()))); + const paramTypeIndex = words.findIndex(word => potentialTypes.includes(this.sanitizeMarkdownSymbol(word.toLowerCase(), { allowSpaces: true }))); let paramType = 'dynamic'; let paramName = defaultParamName; @@ -383,7 +385,7 @@ class Runner { if (paramTypeIndex >= 0) { // if we found a word that looks like a type, use it for the type, and remove it from the array - paramType = this.sanitizeMarkdownSymbol(words[paramTypeIndex]); + paramType = this.sanitizeMarkdownSymbol(words[paramTypeIndex], { allowSpaces: true }); // translate to an actual BRS type if needed paramType = foundTypesTranslation[paramType.toLowerCase()] || paramType; @@ -546,23 +548,24 @@ class Runner { private getNodeFields(manager: TokenManager) { const result = [] as SceneGraphNodeField[]; - const table = manager.getTableByHeaders(['field', 'type', 'default', 'access permission', 'description']); - const rows = manager.tableToObjects(table); - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let description = table.rows[i][4].text; - //the turndown plugin doesn't convert inner html tables, so turn that into markdown too - description = turndownService.turndown(description); - result.push({ - name: this.sanitizeMarkdownSymbol(row.field), - type: this.sanitizeMarkdownSymbol(row.type), - default: this.sanitizeMarkdownSymbol(row.default, true), - accessPermission: this.sanitizeMarkdownSymbol(row['access permission']), - //grab all the markdown from the 4th column (description) - description: description - }); + const tables = manager.getAllTablesByHeaders(['field', 'type', 'default', 'access permission', 'description']); + for (const table of tables) { + const rows = manager.tableToObjects(table); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let description = table.rows[i][4].text; + //the turndown plugin doesn't convert inner html tables, so turn that into markdown too + description = turndownService.turndown(description); + result.push({ + name: this.sanitizeMarkdownSymbol(row.field), + type: this.sanitizeMarkdownSymbol(row.type, { allowSquareBrackets: true, allowSpaces: true }), + default: this.sanitizeMarkdownSymbol(row.default, { allowSquareBrackets: true, allowSpaces: true }), + accessPermission: this.sanitizeMarkdownSymbol(row['access permission'], { allowSpaces: true }), + //grab all the markdown from the 4th column (description) + description: description + }); + } } - return result; } @@ -599,6 +602,49 @@ class Runner { return result; } + /** + * ContentNode fields are a special case becuase the fields are listed in a different markdown file: + * https://developer.roku.com/docs/developer-program/getting-started/architecture/content-metadata.md + */ + private async buildContentNodeFields() { + const manager = await new TokenManager().process(this.getContentNodeDocApiUrl()); + let keepGoing = true; + const fields: SceneGraphNodeField[] = [ + ...this.getContentNodeFields(manager, ['attributes', 'type', 'values', 'example'], 2), + ...this.getContentNodeFields(manager, ['attribute', 'type', 'values', 'example'], 2), + ...this.getContentNodeFields(manager, ['attribute', 'type', 'values', 'description'], 3) + + ]; + this.result.nodes['contentnode'].fields = fields; + } + + private getContentNodeFields(manager: TokenManager, searchHeaders: string[], descriptionIndex: number, propertyMap?: { name?: string; type?: string }) { + const tables = manager.getAllTablesByHeaders(searchHeaders); + const fields = [] as SceneGraphNodeField[]; + const nameKey = propertyMap?.name ?? searchHeaders[0]; + const typeKey = propertyMap?.type ?? 'type'; + + for (const table of tables) { + const rows = manager.tableToObjects(table); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let description = table.rows[i][descriptionIndex].text; + //the turndown plugin doesn't convert inner html tables, so turn that into markdown too + description = turndownService.turndown(description); + fields.push({ + name: this.sanitizeMarkdownSymbol(row[nameKey]), + type: this.sanitizeMarkdownSymbol(row[typeKey], { allowSquareBrackets: true, allowSpaces: true }).split(':')?.[0], + default: 'not specified', + accessPermission: 'READ_WRITE', + //grab all the markdown from the 4th column (description) + description: description + }); + } + } + return fields; + } + + private isTable(element) { return element?.nodeName?.toLowerCase() === 'table'; } @@ -679,9 +725,17 @@ class Runner { if (method) { manager.setDeprecatedData(method, methodHeader, nextMethodHeader); - method.description = manager.getNextToken( - manager.find(x => !!/description/i.exec(x?.text), methodHeader, nextMethodHeader) - )?.text; + method.description = ( + manager.find(x => { + if (x === methodHeader || /^\**description/i.exec(x?.text) || /^_?available\s*since/i.exec(x?.text)) { + return false; + } + return x.type === 'paragraph'; + }, methodHeader, nextMethodHeader) as marked.Tokens.Paragraph)?.text; + + if (!method.description) { + method.description = manager.getNextToken(methodHeader)?.text; + } method.returnDescription = manager.getNextToken( manager.find(x => !!/return\s*value/i.exec(x?.text), methodHeader, nextMethodHeader) @@ -708,11 +762,17 @@ class Runner { return result; } - private sanitizeMarkdownSymbol(symbolName: string, allowSquareBrackets = false) { - if (allowSquareBrackets) { - return symbolName?.replaceAll(/[\\]/g, ''); + private sanitizeMarkdownSymbol(symbolName: string, opts?: { allowSquareBrackets?: boolean; allowSpaces?: boolean }) { + let result = symbolName; + if (opts?.allowSquareBrackets) { + result = symbolName?.replaceAll(/[\\]/g, ''); + } else { + result = symbolName?.replaceAll(/[\[\]\\]/g, ''); + } + if (!opts?.allowSpaces) { + result = symbolName?.split(' ')?.[0]; } - return symbolName?.replaceAll(/[\[\]\\]/g, ''); + return result; } @@ -722,7 +782,13 @@ class Runner { private getMethod(text: string) { // var state = new TranspileState(new BrsFile({ srcPath: '', destPath: '', program: new Program({})}); - const functionSignatureToParse = `function ${this.fixFunctionParams(this.sanitizeMarkdownSymbol(text))}\nend function`; + let functionSignatureToParse = `function ${this.fixFunctionParams(this.sanitizeMarkdownSymbol(text, { allowSpaces: true }))}\nend function`; + const variadicRegex = new RegExp(/,?\s*\.\.\.\s*\)/, 'g'); // looks for " ...)" + const variadicMatch = functionSignatureToParse.match(variadicRegex); + if (variadicMatch) { + functionSignatureToParse = functionSignatureToParse.replace(variadicRegex, ')'); + } + const { statements } = Parser.parse(functionSignatureToParse); if (statements.length > 0) { const func = statements[0] as FunctionStatement; @@ -732,6 +798,10 @@ class Runner { returnType: func.func.returnTypeExpression?.getType({ flags: SymbolTypeFlag.typetime })?.toTypeString() ?? 'Void' } as Func; + if (variadicMatch) { + signature.isVariadic = true; + } + const paramsRegex = /\((.*?)\)/g; let match = paramsRegex.exec(text); @@ -739,6 +809,9 @@ class Runner { const foundParamTexts = match[1].split(',').map(x => x.replace(/['"]+/g, '').trim()); for (let i = 0; i < foundParamTexts.length; i++) { const foundParam = foundParamTexts[i]; + if (foundParam === '...') { + break; + } signature.params.push(this.getParamFromMarkdown(foundParam, `param${i}`)); } } @@ -753,6 +826,11 @@ class Runner { return `https://developer.roku.com/api/v1/get-dev-cms-doc?locale=en-us&filePath=${docRelativePath.replace(/^\/docs\//, '')}`; } + + private getContentNodeDocApiUrl() { + return this.getDocApiUrl('developer-program/getting-started/architecture/content-metadata.md'); + } + private async loadReferences() { const response = await getJson('https://developer.roku.com/api/v1/get-dev-cms-doc?filePath=left-nav%2Freferences.json&locale=en-us'); this.references = JSON.parse(response.content); @@ -784,6 +862,316 @@ class Runner { interfaces: [], name: 'RSGPalette', url: 'https://developer.roku.com/en-ca/docs/references/scenegraph/scene.md' + }, + contentnode: { + fields: [ + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the channel logo or for an icon that appears beside the program title. See: TimeGrid', + name: 'HDSmallIconUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the item poster when the screen resolution is set to HD. HDGRIDPOSTERURL is used if non-empty. HDPOSTERURL is used otherwise. See: PosterGrid', + name: 'HDGridPosterUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the item poster when the screen resolution is set to HD. HDGRIDPOSTERURL is used if non-empty. HDPOSTERURL is used otherwise. See: PosterGrid', + name: 'HDPosterUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the item poster when the screen resolution is set to HD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid', + name: 'SDGridPosterUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the item poster when the screen resolution is set to SD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid', + name: 'SDPosterUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the item poster when the screen resolution is set to SD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid', + name: 'SDPosterUrl', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The text for the first grid item caption.', + name: 'ShortDescriptionLine1', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The text for the second grid item caption.', + name: 'ShortDescriptionLine2', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'When the fixedLayout field is set to true, this specifies the first row of the grid occupied by this item, where 0 refers to the first row. Note that there can be more rows in the data than visible rows, where the number of visible rows is specified by the numRows field.\nFor example, if the data model contains enough data to fill 12 rows, X would be set to a value from 0 to 11.', + name: 'X', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'When the fixedLayout field is set to true, this specifies the first column of the grid occupied by this item, where 0 refers to the first column. Note that the number of columns is always specified by the numColumns field, regardless of how many items are in the data model.\nFor example, if the numColumns field is set to 3, Y would be set to 0, 1 or 2.', + name: 'Y', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'When the fixedLayout field is set to true, this specifies how many columns the grid item occupies. If not specified, the default value of 1 is used.\nFor example, if the numColumns field were set to 3 and a grid item is to occupy the rightmost two columns, X would be set to 1 and W would be set to 2.', + name: 'W', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'When the fixedLayout field is set to true, this specifies how many rows the grid item occupies. If not specified, the default value of 1 is used.\nFor example, if a grid item is to occupy the the third, fourth and fifth rows, Y would be set to 2 and H would be set to 3.', + name: 'H', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'Overrides the `caption1NumLines` field for this section of the grid, allowing different sections to display different caption layouts. If not specified, the value of the `caption1NumLines` field is used.', + name: 'GridCaption1NumLines', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'Overrides the `caption2NumLines` field for this section of the grid, allowing different sections to display different caption layouts. If not specified, the value of the `caption2NumLines` field is used.', + name: 'GridCaption1NumLines', + type: 'integer' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the icon to be displayed to the left of the list item label when the list item is not focused', + name: 'HDListItemIconURL', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'The image file for the icon to be displayed to the left of the list item label when the list item is focused', + name: 'HDListItemIconSelectedURL', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: 'not specified', + description: 'When set to true, the default, the list item displays the checkbox icon, reflecting the item\'s current selection state. When set to false, no checkbox icon is displayed, allowing the list to contain a mix of checkbox and regular list items.', + name: 'HideIcon', + type: 'boolean' + } + ] + }, + bifdisplay: { + description: 'Component that displays BIFs and allows navigation.', + events: [], + extends: { + name: 'Node', + url: 'https://developer.roku.com/docs/references/scenegraph/node.md' + }, + fields: [ + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'A color to be blended with the image displayed behind individual BIF images displayed on the screen. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'frameBgBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'The URI of an image to be displayed behind individual frames on the screen. The actual frame image is displayed opaquely on top of this background, so only the outer edges of this image are visible. Because of that, this background image typically appears as a border around the video frame. If the frameBgBlendColor field is set to a value other than the default, that color will be blended with the background image.', + name: 'frameBgImageUri', + type: 'uri' + }, + { + accessPermission: 'WRITE_ONLY', + default: 'invalid', + description: 'Requests the nearest BIF to the time specified. This would normally be an offset from the current playback position. The getNearestFrame request is passed to the BifCache which uses the getNearestFrame() method implemented on all BIF storage classes. Existing BifCache functionality is then used to retrieve the bitmap data and load it into the texture manager.', + name: 'getNearestFrame', + type: 'time' + }, + { + accessPermission: 'READ_ONLY', + default: 'invalid', + description: 'Contains the URI of the requested BIF. The returned URIs will be of the form `memory://BIF%d%d`. These URIs can then be used directly in the `uri` field of a Poster SGN (or similar).', + name: 'nearestFrame', + type: 'string' + } + ], + interfaces: [], + name: 'BifDisplay', + url: 'https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields' + }, + trickplaybar: { + description: 'The visible TrickPlayBar node.', + events: [], + extends: { + name: 'Node', + url: 'https://developer.roku.com/docs/references/scenegraph/node.md' + }, + fields: [ + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'This is blended with the marker for the current playback position. This is typically a small vertical bar displayed in the TrickPlayBar node when the user is fast-forwarding or rewinding through the video.', + name: 'currentTimeMarkerBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: 'system default', + description: 'Sets the color of the text next to the trickPlayBar node indicating the time elapsed/remaining.', + name: 'textColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'Sets the blend color of the square image in the trickPlayBar node that shows the current position, with the current direction arrows or pause icon on top. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'thumbBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'This color will be blended with the graphical image specified in the `filledBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'filledBarBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'The color of the trickplay progress bar to be blended with the `filledBarImageUri` for live linear streams.', + name: 'liveFilledBarBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'A 9-patch or ordinary PNG of the bar that represents the completed portion of the work represented by this ProgressBar node. This is typically displayed on the left side of the track. This will be blended with the color specified by the `filledBarBlendColor` field, if set to a non-default value.', + name: 'filledBarImageUri', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'This color is blended with the graphical image specified by `trackImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'trackBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.', + name: 'trackImageUri', + type: 'uri' + } + ], + interfaces: [], + name: 'TrickPlayBar', + url: 'https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields' + }, + progressbar: { + description: 'Component that shows the progress of re-buffering, after video playback has started.', + events: [], + extends: { + name: 'Node', + url: 'https://developer.roku.com/docs/references/scenegraph/node.md' + }, + fields: [ + { + accessPermission: 'READ_WRITE', + default: 'system default', + description: 'Sets a custom width for an instance of the ProgressBar node.', + name: 'width', + type: 'float' + }, + { + accessPermission: 'READ_WRITE', + default: 'system default', + description: 'Sets a custom width for an instance of the ProgressBar node.', + name: 'height', + type: 'float' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'A color to be blended with the graphical image specified in the `emptyBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'emptyBarBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'A 9-patch or ordinary PNG of the bar presenting the remaining work to be done. This is typically displayed on the right side of the track, and is blended with the color specified in the `emptyBarBlendColor` field, if set to a non-default value.', + name: 'emptyBarImageUri', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'This color will be blended with the graphical image specified in the `filledBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'filledBarBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'A 9-patch or ordinary PNG of the bar that represents the completed portion of the work represented by this ProgressBar node. This is typically displayed on the left side of the track. This will be blended with the color specified by the `filledBarBlendColor` field, if set to a non-default value.', + name: 'filledBarImageUri', + type: 'uri' + }, + { + accessPermission: 'READ_WRITE', + default: '0xFFFFFFFF', + description: 'This color is blended with the graphical image specified by `trackImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.', + name: 'trackBlendColor', + type: 'color' + }, + { + accessPermission: 'READ_WRITE', + default: '""', + description: 'A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.', + name: 'trackImageUri', + type: 'uri' + }, + + { + accessPermission: 'READ_WRITE', + default: 'not sepcified', + description: 'A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.', + name: 'percentage', + type: 'integer' + } + ], + interfaces: [], + name: 'TrickPlayBar', + url: 'https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields' } }, components: { @@ -798,25 +1186,6 @@ class Runner { interfaces: {} }); - // Override ifSGNodeDict.callFunc - fixMethod(this.result.interfaces.ifsgnodedict, 'callfunc', { - // Taken from: https://developer.roku.com/en-ca/docs/references/brightscript/interfaces/ifsgnodedict.md#callfunc - description: `callFunc() is a synchronized interface on roSGNode. It will always execute in the component's owning ScriptEngine and thread (by rendezvous if necessary), and it will always use the m and m.top of the owning component. Any context from the caller can be passed via one or more method parameters, which may be of any type (previously, callFunc() only supported a single associative array parameter).\n\nTo call the function, use the \`callFunc\` field with the required method signature. A return value, if any, can be an object that is similarly arbitrary. The method being called must determine how to interpret the parameters included in the \`callFunc\` field.`, - name: 'callFunc', - params: [ - { - default: null, - description: 'The function name to call.', - isRequired: true, - name: 'functionName', - type: 'String' - } - ], - isVariadic: true, - returnType: 'Dynamic', - returnDescription: 'An arbitrary object' - }); - // fix ifStringOp overloads fixOverloadedMethod(this.result.interfaces.ifstringops, 'instr'); fixOverloadedMethod(this.result.interfaces.ifstringops, 'mid'); @@ -829,6 +1198,13 @@ class Runner { // fix ifdraw2d overloads fixOverloadedMethod(this.result.interfaces.ifdraw2d, 'drawScaledObject'); + + // fix ifToStr overloads + fixOverloadedMethod(this.result.interfaces.iftostr, 'toStr'); + + //fix roSGNodeContentNode overloads + fixOverloadedField(this.result.nodes.contentnode, 'actors'); + fixOverloadedField(this.result.nodes.contentnode, 'categories'); } } @@ -914,6 +1290,24 @@ function fixOverloadedMethod(iface: RokuInterface, funcName: string) { iface.methods.push(mergedFunc); } +function fixOverloadedField(node: SceneGraphNode, fieldName: string) { + const fieldsWithName = node.fields.filter(f => f.name.toLowerCase() === fieldName.toLowerCase()); + if (fieldsWithName.length < 2) { + return; + } + const filteredFields = node.fields.filter(f => f.name.toLowerCase() !== fieldName.toLowerCase()); + + const unionfield = fieldsWithName[0]; + + for (let i = 1; i < fieldsWithName.length; i++) { + unionfield.description += ` or ${fieldsWithName[i].description}`; + unionfield.type += ` or ${fieldsWithName[i].type}`; + } + filteredFields.push(unionfield); + + node.fields = filteredFields; +} + function fixMethod(iface: RokuInterface, funcName: string, mergeData: Func) { const index = iface?.methods.findIndex(method => method.name.toLowerCase() === funcName.toLowerCase()); @@ -1129,6 +1523,31 @@ class TokenManager { } } + /** + * Scan the tokens and find the all top-level tables based on the header names + */ + public getAllTablesByHeaders(searchHeaders: string[], startAt?: Token, endTokenMatcher?: EndTokenMatcher): TableEnhanced[] { + let startIndex = this.tokens.indexOf(startAt); + startIndex = startIndex > -1 ? startIndex : 0; + const tables = []; + for (let i = startIndex + 1; i < this.tokens.length; i++) { + const token = this.tokens[i]; + if (token?.type === 'table') { + const headers = token?.header?.map(x => x.text.toLowerCase()); + if ( + headers.every(x => searchHeaders.includes(x)) && + searchHeaders.every(x => headers.includes(x)) + ) { + tables.push(token as TableEnhanced); + } + } + if (endTokenMatcher?.(token) === true) { + break; + } + } + return tables; + } + /** * Convert a markdown table token into an array of objects with the headers as keys, and the cell values as values */ diff --git a/src/Program.spec.ts b/src/Program.spec.ts index b95543e2a..a78f3bb20 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -32,7 +32,7 @@ import { ComponentType } from './types/ComponentType'; import { ArrayType } from './types/ArrayType'; import { AssociativeArrayType } from './types/AssociativeArrayType'; import { BooleanType } from './types/BooleanType'; -import { DoubleType } from './types'; +import { DoubleType, UnionType } from './types'; const sinon = createSandbox(); @@ -2807,6 +2807,62 @@ describe('Program', () => { expect(ifAAType.getMemberType('ifAssociativeArray', { flags: SymbolTypeFlag.runtime }).isResolvable()).to.be.false; }); + + it('has various hard to decipher field types', () => { + const table = program.globalScope.symbolTable; + const opts = { flags: SymbolTypeFlag.runtime }; + const recType = table.getSymbolType('roSGNodeRectangle', { flags: SymbolTypeFlag.typetime }); + expectTypeToBe(recType.getMemberType('color', opts), UnionType); + + const stdKeyDialogType = table.getSymbolType('roSGNodeStandardKeyboardDialog', { flags: SymbolTypeFlag.typetime }); + expectTypeToBe(stdKeyDialogType.getMemberType('textEditBox', opts), ComponentType); + }); + + it('has content node and all its attributes', () => { + const table = program.globalScope.symbolTable; + const opts = { flags: SymbolTypeFlag.runtime }; + const cnType = table.getSymbolType('roSGNodeContentNode', { flags: SymbolTypeFlag.typetime }); + + expectTypeToBe(cnType.getMemberType('contentType', opts), StringType); + + const actorsType = cnType.getMemberType('actors', opts) as UnionType; + expectTypeToBe(actorsType, UnionType); + expectTypeToBe(actorsType.types[0], ArrayType); + expectTypeToBe(actorsType.types[1], StringType); + + const cdnConfigType = cnType.getMemberType('cdnConfig', opts) as ArrayType; + expectTypeToBe(cdnConfigType, ArrayType); + expectTypeToBe(cdnConfigType.defaultType, AssociativeArrayType); + + const subTitleTracksType = cnType.getMemberType('SubtitleTracks', opts) as ArrayType; + expectTypeToBe(subTitleTracksType, ArrayType); + expectTypeToBe(subTitleTracksType.defaultType, AssociativeArrayType); + + const subtitleConfigType = cnType.getMemberType('SUBTITLECONFIG', opts); + expectTypeToBe(subtitleConfigType, AssociativeArrayType); + + const hdlisticonSelType = cnType.getMemberType('HDListItemIconSelectedURL', opts); + expectTypeToBe(hdlisticonSelType, StringType); + }); + + it('has video player internal nodes', () => { + const table = program.globalScope.symbolTable; + const opts = { flags: SymbolTypeFlag.runtime }; + const videoType = table.getSymbolType('roSGNodeVideo', { flags: SymbolTypeFlag.typetime }); + expectTypeToBe(videoType, ComponentType); + + const bifDisplayType = videoType.getMemberType('bifDisplay', opts) as ComponentType; + expectTypeToBe(bifDisplayType, ComponentType); + expect(bifDisplayType.name).to.eq('BifDisplay'); + expectTypeToBe(bifDisplayType.getMemberType('getNearestFrame', opts), DoubleType); + expectTypeToBe(bifDisplayType.getMemberType('frameBgImageUri', opts), StringType); + + const trickBarType = videoType.getMemberType('trickPlayBar', opts) as ComponentType; + expectTypeToBe(trickBarType, ComponentType); + expect(trickBarType.name).to.eq('TrickPlayBar'); + expectTypeToBe(trickBarType.getMemberType('trackImageUri', opts), StringType); + expectTypeToBe(trickBarType.getMemberType('trackBlendColor', opts), UnionType); + }); }); describe('manifest', () => { diff --git a/src/Program.ts b/src/Program.ts index c3d7d114e..6b65d10fe 100644 --- a/src/Program.ts +++ b/src/Program.ts @@ -187,10 +187,6 @@ export class Program { this.globalScope.symbolTable.addSymbol(ifaceData.name, { description: ifaceData.description }, nodeType, SymbolTypeFlag.typetime); } - for (const nodeData of Object.values(nodes) as SGNodeData[]) { - this.recursivelyAddNodeToSymbolTable(nodeData); - } - for (const componentData of Object.values(components) as BRSComponentData[]) { const nodeType = new InterfaceType(componentData.name); nodeType.addBuiltInInterfaces(); @@ -200,6 +196,10 @@ export class Program { } } + for (const nodeData of Object.values(nodes) as SGNodeData[]) { + this.recursivelyAddNodeToSymbolTable(nodeData); + } + for (const eventData of Object.values(events) as BRSEventData[]) { const nodeType = new InterfaceType(eventData.name); nodeType.addBuiltInInterfaces(); diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts index c14bd7237..0873a334e 100644 --- a/src/bscPlugin/validation/ScopeValidator.spec.ts +++ b/src/bscPlugin/validation/ScopeValidator.spec.ts @@ -2276,6 +2276,19 @@ describe('ScopeValidator', () => { ]); }); + + it('allows assigning string to font fields', () => { + program.setFile('source/util.bs', ` + sub setLabelFont(label as roSGNodeLabel) + label.font = "font:LargeSystemFont" + label.font.size = 50 + end sub + `); + program.validate(); + //should have no errors + expectZeroDiagnostics(program); + }); + }); describe('operatorTypeMismatch', () => { diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts index 72a3a40cb..70f2b0a58 100644 --- a/src/bscPlugin/validation/ScopeValidator.ts +++ b/src/bscPlugin/validation/ScopeValidator.ts @@ -1,6 +1,6 @@ import { URI } from 'vscode-uri'; import type { Range } from 'vscode-languageserver'; -import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBrsFile, isCallExpression, isCallableType, isClassStatement, isClassType, isConstStatement, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumStatement, isEnumType, isFunctionExpression, isFunctionParameterExpression, isFunctionStatement, isInterfaceStatement, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isObjectType, isPrimitiveType, isReferenceType, isTypedFunctionType, isUnionType, isVariableExpression, isXmlScope } from '../../astUtils/reflection'; +import { isAliasStatement, isAssignmentStatement, isAssociativeArrayType, isBrsFile, isCallExpression, isCallableType, isClassStatement, isClassType, isComponentType, isConstStatement, isDottedGetExpression, isDynamicType, isEnumMemberType, isEnumStatement, isEnumType, isFunctionExpression, isFunctionParameterExpression, isFunctionStatement, isInterfaceStatement, isLiteralExpression, isNamespaceStatement, isNamespaceType, isNewExpression, isObjectType, isPrimitiveType, isReferenceType, isStringType, isTypedFunctionType, isUnionType, isVariableExpression, isXmlScope } from '../../astUtils/reflection'; import { Cache } from '../../Cache'; import type { DiagnosticInfo } from '../../DiagnosticMessages'; import { DiagnosticMessages } from '../../DiagnosticMessages'; @@ -512,7 +512,14 @@ export class ScopeValidator { return; } - const accessibilityIsOk = this.checkMemberAccessibility(file, dottedSetStmt, typeChainExpectedLHS); + let accessibilityIsOk = this.checkMemberAccessibility(file, dottedSetStmt, typeChainExpectedLHS); + + //special case for roSgNodeFont - these can accept string + if (isComponentType(expectedLHSType) && expectedLHSType.name.toLowerCase() === 'font') { + if (isStringType(actualRHSType)) { + return; + } + } if (accessibilityIsOk && !expectedLHSType?.isTypeCompatible(actualRHSType, compatibilityData)) { this.addMultiScopeDiagnostic({ diff --git a/src/roku-types/data.json b/src/roku-types/data.json index 378538d6d..26f1d7e11 100644 --- a/src/roku-types/data.json +++ b/src/roku-types/data.json @@ -1,5 +1,5 @@ { - "generatedDate": "2024-02-09T15:23:27.876Z", + "generatedDate": "2024-05-08T16:30:56.090Z", "nodes": { "animation": { "description": "Extends [**AnimationBase**](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/animationbase.md\n\nThe Animation node class provides animations of renderable nodes, by applying interpolator functions to the values in specified renderable node fields. For an animation to take effect, an Animation node definition must include a child field interpolator node ([FloatFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/floatfieldinterpolator.md\"FloatFieldInterpolator\"), [Vector2DFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/vector2dfieldinterpolator.md\"Vector2DFieldInterpolator\"), [ColorFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/colorfieldinterpolator.md\"ColorFieldInterpolator\")) definition for each renderable node field that is animated.\n\nThe Animation node class provides a simple linear interpolator function, where the animation takes place smoothly and simply from beginning to end. The Animation node class also provides several more complex interpolator functions to allow custom animation effects. For example, you can move a graphic image around the screen at differing speeds and curved trajectories at different times in the animation by specifying the appropriate function in the easeFunction field (quadratic and exponential are two examples of functions that can be specified). The interpolator functions are divided into two parts: the beginning of the animation (ease-in), and the end of the animation (ease-out). You can apply a specified interpolator function to either or both ease-in and ease-out, or specify no function for either or both (which is the linear function). You can also change the portion of the animation that is ease-in and ease-out to arbitrary fractional values for a quadratic interpolator function applied to both ease-in and ease-out.", @@ -48,7 +48,7 @@ "accessPermission": "READ_ONLY", "default": "false", "description": "Indicates whether the animation runs or jumps to the end (effectively skipping the animation and rendering it in its final state).", - "name": "willBeSkipped (_Available since Roku OS 10.0_)", + "name": "willBeSkipped", "type": "boolean" } ], @@ -389,7 +389,7 @@ "accessPermission": "READ_WRITE", "default": "false", "description": "Specifies whether changes in the focus item should be animated. If this field is set to true, any scrolling or repositioning/scaling of the focus indicator occurs without an animation. This causes fields reflecting the focus status (itemFocused, currFocusRow, currFocusColumn) to be updated instantly and not transition smoothly between old and new values. For example, currFocusRow will go directly from 3.0 to 4.0 instead of taking on values between 3.0 and 4.0.", - "name": "skipFocusAnimations _Available since Roku OS 11.5_", + "name": "skipFocusAnimations", "type": "Boolean" }, { @@ -606,6 +606,47 @@ "name": "Audio", "url": "https://developer.roku.com/docs/references/scenegraph/media-playback-nodes/audio.md" }, + "bifdisplay": { + "description": "Component that displays BIFs and allows navigation.", + "events": [], + "extends": { + "name": "Node", + "url": "https://developer.roku.com/docs/references/scenegraph/node.md" + }, + "fields": [ + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "A color to be blended with the image displayed behind individual BIF images displayed on the screen. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "frameBgBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "The URI of an image to be displayed behind individual frames on the screen. The actual frame image is displayed opaquely on top of this background, so only the outer edges of this image are visible. Because of that, this background image typically appears as a border around the video frame. If the frameBgBlendColor field is set to a value other than the default, that color will be blended with the background image.", + "name": "frameBgImageUri", + "type": "uri" + }, + { + "accessPermission": "WRITE_ONLY", + "default": "invalid", + "description": "Requests the nearest BIF to the time specified. This would normally be an offset from the current playback position. The getNearestFrame request is passed to the BifCache which uses the getNearestFrame() method implemented on all BIF storage classes. Existing BifCache functionality is then used to retrieve the bitmap data and load it into the texture manager.", + "name": "getNearestFrame", + "type": "time" + }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Contains the URI of the requested BIF. The returned URIs will be of the form `memory://BIF%d%d`. These URIs can then be used directly in the `uri` field of a Poster SGN (or similar).", + "name": "nearestFrame", + "type": "string" + } + ], + "interfaces": [], + "name": "BifDisplay", + "url": "https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields" + }, "busyspinner": { "description": "Extends [**Group**](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/group.md\n\nThe BusySpinner node class is a simple widget that displays a continuously rotating bitmap. Since the BusySpinner node class uses an internal Poster node instance, the busy spinner bitmap can be specified by setting the internal Poster node uri field.\n\n[SimpleBusySpinner](https://github.com/rokudev/samples/tree/master/ux%20components/widgets) is a sample channel that demonstrates usage of the BusySpinner.\n\n> Not all Roku Player hardware versions support arbitrary rotations. In particular, some hardware versions only support 90 degree rotation increments. In those cases, the icon will step through 90 degree, 180 degree, 270 degree and back to 0 degree rotations, rather than spin smoothly.", "events": [], @@ -637,7 +678,7 @@ }, { "accessPermission": "READ_WRITE", - "default": "1", + "default": "2", "description": "The number of seconds to complete a 360-degree rotation of the spinner image. A value of 0 will cause the spinner to remain stationary and not spin", "name": "spinInterval", "type": "time" @@ -891,12 +932,110 @@ "url": "https://developer.roku.com/docs/references/scenegraph/node.md" }, "fields": [ + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*getCatalog\\*\\*\\](#getcatalog) command.", + "name": "catalog", + "type": "ContentNode" + }, { "accessPermission": "READ_WRITE", "default": "", "description": "Specifies the command to be executed: \\* \\[getUserData\\](#getuserdata) \\* \\[getUserRegionData\\](#getuserregiondata) () \\* \\[getCatalog\\](#getcatalog) and \\[getStoreCatalog\\](#getstorecatalog) \\* \\[doOrder\\](#doorder) \\* \\[getPurchases\\](#getpurchases) and \\[getAllPurchases\\](#getallpurchases) \\* \\[storeChannelCredData\\](#storechannelcreddata) \\* \\[getChannelCred\\](#getchannelcred) \\* \\[getDeviceAttestationToken\\](#getdeviceattestationtoken) (\\_Available since Roku OS 11.5\\_) \\* \\[requestPartnerOrder\\](#requestpartnerorder) \\* \\[confirmPartnerOrder\\](#confirmpartnerorder)", "name": "command", "type": "string" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Confirms the product being ordered from a TVOD channel. The order contains the following fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| orderId | string | The orderID returned by Roku in the [RequestPartnerOrderStatus](#requestpartnerorderstatus) content node. |\n| code | string | The product identifier. |\n| priceDisplay | string | The original price of the product. Do not include a currency symbol (for example, set this to \"3.99\" instead of \"$3.99\"). |\n| price | string | The final price of the product, including any discounts. Do not include a currency symbol (for example, set this to \"3.99\" instead of \"$3.99\"). |\n| title | string | The name of the product to be displayed on customers' invoices. |\n| couponCode | string | An alphanumeric string entered by the customer to receive a discounted price on the product. |\n| contentKey | string | The publisher-specific SKU (or other unique identifier) for the product. |", + "name": "confirmPartnerOrder", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*confirmPartnerOrder\\*\\*\\](#confirmpartnerorder) command.", + "name": "confirmPartnerOrderStatus", + "type": "ContentNode" + }, + { + "accessPermission": "WRITE_ONLY", + "default": "{}", + "description": "Enables the \\[\\*\\*order\\*\\*\\](#order) field to be populated incrementally. Each time this field is set, the \\*\\*order\\*\\* field is modified. The \\*\\*deltaOrder\\*\\* associative array should contain a \"code\" string that identifies an available item, and a \"qty\" integer value to indicate how the children of the order field \\*\\*ContentNode\\*\\* should be modified. For example, if the order is invalid, setting the deltaOrder field to the following associative array:   \\`{ \"code\": \"Merchandise1\", \"qty\": 1 }\\` Would cause an order field to be set to a \\*\\*ContentNode\\*\\*, with one child \\*\\*ContentNode\\*\\* with a \"code\" field set to \"Merchandise1\", and a \"qty\" field set to 1. If the deltaOrder field was then set to:   \\`{ \"code\": \"MyItem2\", \"qty\": 1 }\\` The order field \\*\\*ContentNode\\*\\* would have a second \\*\\*ContentNode\\*\\* child appended to it, with the specified \"code\" and \"qty\" field values. The \"qty\" field can be set to a negative value to remove an item from an order. For example, if the order field was set as above, and the deltaOrder field was set to:   \\`{ \"code\" MyItem2\", \"qty\": -1 }\\` The order field \\*\\*ContentNode\\*\\* would have the second child \\*\\*ContentNode\\*\\* removed.", + "name": "deltaOrder", + "type": "associative array" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "Enables a test mode for the \\*\\*ChannelStore\\*\\* node. The test mode disables communication by the ChannelStore node with the Roku Channel Store server, and it causes responses to asynchronous queries and operations to come from XML test configuration files rather than the server. To use this test method, create a \\*\\*csFake\\*\\* folder and add the following XML files to it in order to simulate web service request and response data: \\* \\*\\*csfake/GetCatalog.xml\\*\\*: Simulates the list of products available for purchase in the channel. \\* \\*\\*csfake/GetPurchases.xml\\*\\*: Simulates the list of products already purchased by the user. \\* \\*\\*csfake/PlaceOrder.xml\\*\\*: Contains information about the product to be ordered. \\* \\*\\*csfake/CheckOrder.xml\\*\\*: Verifies the validity of the order placed. For example, if the \\*\\*order\\*\\* and \\*\\*id\\*\\* values in the PlaceOrder and CheckOrder XML files do not match, the fake server will report an error in the order processing. See the \\[SimpleChannelStore sample channel\\](https://github.com/rokudev/samples/tree/master/roku%20pay/SimpleChannelStore/csfake) for how to use this testing method. The \\*\\*fakeServer\\*\\* field must be set to false in a published channel to allow actual \\[In-Channel Product\\](/docs/developer-program/roku-pay/quickstart/in-channel-products.md) purchases by users. It is recommended that developers use \\[billing testing\\](/docs/developer-program/roku-pay/testing/billing-testing.md) instead of the fakeServer.", + "name": "fakeServer", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the order to be filled when the \\[\\*\\*doOrder\\*\\*\\](#doorder) command is executed. This ContentNode contains one child ContentNode for each of the items to be purchased. The child ContentNode must contain the following fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| code | string | Identifies the product to be purchased, as entered in the **Product Identifier** field on the [In-Channel Product page in the Developer Dashboard](https://developer.roku.com/products) when the product was created. See [Creating an order](#creating-an-order) for more information. |\n| qty | string | The quantity of the item to be purchased, which is typically 1 for most in-channel products. This is only typically more than 1 if the product is a \"packet\" of identical items (such as game points, number of viewings permitted of some item of content, and so on). |\n| action | string | **For upgrades/downgrades only**. Set this to \"Upgrade\" or \"Downgrade\" to change the subscription plan from a previous purchase (for example, `myOrder.action = \"Upgrade\"`). The required values are case-sensitive; do not pass \"upgrade\" or \"downgrade\". See [On-device upgrade and downgrade](/docs/developer-program/roku-pay/implementation/on-device-upgrade-downgrade.md) for more information. |\n\nTo clear an order, set the \\*\\*order\\*\\* field to \"invalid\".", + "name": "order", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of the \\[\\*\\*doOrder\\*\\*\\](#doorder) command.", + "name": "orderStatus", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*getPurchases\\*\\*\\](#getpurchases) or \\[\\*\\*getAllPurchases\\*\\*\\](#getallpurchases) command.", + "name": "purchases", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "all", + "description": "Specifies the Roku customer account fields to be retrieved when the \\[\\*\\*getUserData\\*\\*\\](#getuserdata) command is executed. The default value is \"all\", which causes a ContentNode object to be returned from \\*\\*getUserData\\*\\* that includes all of the available Roku customer account information. To request specific Roku customer account information items (for example, an email address, first name, and last name) set this field to a string containing a comma-separated list of values (for example, \"email, firstname, lastname\"). The available values are as follows: \\* email \\* phone \\* firstname \\* lastname \\* street \\* city \\* state \\* zip \\* country \\* birth (\\_available since Roku OS 10.0\\_) \\* gender (\\_available since Roku OS 10.0\\_) In this case, the ContentNode object returned from the \\*\\*getUserData\\*\\* command includes the specified customer account information.", + "name": "requestedUserData", + "type": "string" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Specifies whether the RFI screen is used for customer sign-ups or sign-ins. This may be one of the following values:\n\n| Field | Type | Default | Description |\n| --- | --- | --- | --- |\n| context | string | \"signup\" | Specifies the context of the RFI screen, which may be one of the following values: * \"signup\": The RFI screen displays a \"Let's create your account\" title and lists the customer information specified in the [**requestedUserData** field](#requesteduserdata). The RFI screen uses the \"signup\" context by default. See [Sign-up requirements and best practices](/docs/developer-program/roku-pay/signup-best-practices.md) for more information on implementing the channel sign-up UI. * \"signin: \"The RFI screen displays a \"Sign in\" title and lists only email or phone attributes, if specified in the [**requestedUserData** field](#requesteduserdata). Other attributes are ignored, even if specified. See the [Sign-in example](#sign-in-example) for how to use this field. See [Sign-in requirements and best practices](/docs/developer-program/roku-pay/signin-best-practices.md) for more information on implementing the channel sign-in UI. |\n| forceShowData (_Available since Roku OS 11.0_) | Boolean | false | If true, the RFI signup screen displays the values of the requested customer information to be shared with the channel (for example, Jone Doe, jon.doe@emailaddress.com). By default, this flag is set to false, which means that the default RFI screen for the region is used. For example, in the US, the RFI screen displays the type of customer information being requested (email address, name, and so on). This flag has no effect if the context field is set to \"signin\" (the RFI sign-in screen always displays the customer information values). **Example**: ``` store = CreateObject(\"roSGNode\", \"ChannelStore\") ' Doesn't show user data in dialog unless necessary in the user's region. store.requestedUserData = \"email,firstname,lastname,gender,birth\" store.command = \"getUserData\" ' Shows user data in dialog. info = CreateObject(\"roSGNode\", \"ContentNode\") info.addFields({forceShowData: true}) store.requestedUserDataInfo = info store.requestedUserData = \"email\" store.command = \"getUserData\" ``` |", + "name": "requestedUserDataInfo", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Specifies the product to be ordered from a TVOD channel. The order contains the following fields:\n\n| Field | Type | Description |\n| --- | --- | --- |\n| code | string | Identifies the product to be purchased, as entered in the **Product Identifier** field on the [In-Channel Product page in the Developer Dashboard](https://developer.roku.com/products) when the product was created. For TVOD-exclusive channels, a single in-channel product may be used for all orders. A TVOD-exclusive channel only has transactional products such as movie rentals; it does not offer any subscription products. |\n| priceDisplay | string | The original price of the product. Do not include a currency symbol (for example, set this to \"3.99\" instead of \"$3.99\"). |\n| price | string | The final price of the product, including any discounts. Do not include a currency symbol (for example, set this to \"3.99\" instead of \"$3.99\"). |\n| title | string | A description of the product (for example, the name of a rental movie). |\n| couponCode | string | An alphanumeric string entered by the customer to receive a discounted price on the product. |\n| contentKey | string | The publisher-specific SKU (or other unique identifier) for the product. |", + "name": "requestPartnerOrder", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*requestPartnerOrder\\*\\*\\](#requestpartnerorder) command.", + "name": "requestPartnerOrderStatus", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*getStoreCatalog\\*\\*\\](#getstorecatalog) command.", + "name": "storeCatalog", + "type": "ContentNode" + }, + { + "accessPermission": "READ_WRITE", + "default": "invalid", + "description": "Contains the results of a \\[\\*\\*getUserData\\*\\*\\](#getuserdata) command. The value stored in this field depends on whether the user clicks \\*\\*Continue\\*\\* or \\*\\*Cancel\\*\\* in the Request for Information (RFI) screen. If the user clicks \\*\\*Continue\\*\\*, this field is populated with the Roku customer account information that was requested in the \\[\\*\\*requestedUserData\\*\\*\\](#requesteduserdata) field. If the user clicks \\*\\*Cancel\\*\\*, this field is set to \"invalid\".", + "name": "userData", + "type": "ContentNode" } ], "interfaces": [], @@ -1022,33 +1161,713 @@ "type": "string" }, { - "accessPermission": "READ_ONLY", - "default": "\"none\"", - "description": "Indicates the progress of the library download. The possible values are:\n\n| Value | Meaning |\n| --- | --- |\n| none | The default if the library is not being downloaded |\n| loading | Library is downloading |\n| ready | Library has downloaded successfully |\n| failed | Download of the library has failed |", - "name": "loadStatus", - "type": "value string" + "accessPermission": "READ_ONLY", + "default": "\"none\"", + "description": "Indicates the progress of the library download. The possible values are:\n\n| Value | Meaning |\n| --- | --- |\n| none | The default if the library is not being downloaded |\n| loading | Library is downloading |\n| ready | Library has downloaded successfully |\n| failed | Download of the library has failed |", + "name": "loadStatus", + "type": "value string" + }, + { + "accessPermission": "READ_WRITE", + "default": "no default", + "description": "The URL of the library to be downloaded", + "name": "uri", + "type": "uri" + } + ], + "interfaces": [], + "name": "ComponentLibrary", + "url": "https://developer.roku.com/docs/references/scenegraph/control-nodes/componentlibrary.md" + }, + "contentnode": { + "description": "Extends [**Node**](https://developer.roku.com/docs/references/scenegraph/node.md\n\nThe ContentNode class allows you to specify the data used to configure a node or component. Many nodes and components require a ContentNode as the specification of their content field in order to be properly configured. In general, lists, grids, and panels require a ContentNode for configuration. The data included in a ContentNode can be data such as the text for labels in the node or component, and the spacing between items in a list, grid, or panel, including data to create custom lists, grids, and panels. The reference information for every node or component that requires a ContentNode includes a section that details the requirements of the ContentNode for that node or component.\n\nContentNodes defined as the specification for a node or component content field are typically structured as one ContentNode parent node, with a hierarchy of child nodes that specify the actual data, and sections of data if needed. For example, a [LabelList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/labellist.md\"LabelList\") node can have several sections that divide the entire list, each with their own section heading, and specific items in that section of the list. The ContentNode for that LabelList node should have two levels of child ContentNodes, one level for the data to configure the list sections, and then another level of child ContentNodes for the data for each item in that list section.\n\nA ContentNode can also be used to specify the data for custom components with defined interfaces, and for nodes and components that require [Content Meta-Data](/docs/developer-program/getting-started/architecture/content-metadata.md \" Content Meta-Data\"). Also, you should use a ContentNode for complex structures of data for your application rather than associative arrays. ContentNode objects are passed by reference in the application, while associative array objects are copied. For large complex data structures, passing ContentNode objects is much quicker than passing the equivalent associative array object. You can use associative arrays for simpler data structures with just a few fixed members.\n\n> All of the attributes listed in [Content Meta-Data](/docs/developer-program/getting-started/architecture/content-metadata.md \"Content Meta-Data\") can be set as fields in a Content node. However, when creating a Content node, the fields themselves are not created until the valid attributes are set as fields, using either assignment (=), or set using [setField()](https://developer.roku.com/docs/references/brightscript/interfaces/ifsgnodefield.mdsetfieldfieldname-as-string-value-as-object-as-boolean \"setField\") or [setFields()](https://developer.roku.com/docs/references/brightscript/interfaces/ifsgnodefield.mdsetfieldsfields-as-object-as-boolean. \"setFields()\")", + "events": [], + "extends": { + "name": "Node", + "url": "https://developer.roku.com/docs/references/scenegraph/node.md" + }, + "fields": [ + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "List of Actor Names or Individual Actor Name", + "name": "Actors", + "type": "roArray or String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Maximum startup bitrate specified in kbps. Streaming will start with a variant less than or equal to this value. If this value is not set, it will default to 2500 kbps.", + "name": "AdaptiveMaxStartBitrate", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Minimum startup bitrate specified in kbps. Streaming will start with a variant equal to or greater than this value. If this value is not set or if it's set to zero, any minimum start bitrate will be ignored.", + "name": "AdaptiveMinStartBitrate", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSpringboard audio style uses this to display the album", + "name": "Album", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSpringboard audio style uses to show artist", + "name": "Artist", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Comma-separated list of audio tracks (based on ISO 639-1 or 639-2 language code) that may not be selected from the \\*\\*Audio track\\*\\* setting for the content. (Available since Roku OS 9.4) If a language is both blacklisted and whitelisted, the blacklisting takes precedence.", + "name": "AudioBlacklist", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSpringboardScreen: If set to \"dolby-digital\", will display a \"5.1 ))\" icon in the lower left of a movie style springboard screen", + "name": "AudioFormat", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "\\*\\*This attribute was deprecated as of the Roku 9.2 OS release.\\*\\* Users can select their preferred audio language on-device in the \\*\\*Settings > Audio > Audio Preferred Language\\*\\* screen.", + "name": "AudioLanguageSelected", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "\\*\\*This attribute is deprecated\\*\\* Users can select their preferred audio language on-device in the \\*\\*Settings > Audio > Audio Preferred Language\\*\\* screen.", + "name": "AudioPIDPref", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Comma-separated list of audio tracks (based on ISO 639-1 or ISO 639-2 language code) that may be selected from the \\*\\*Audio track\\*\\* setting for the content.", + "name": "AudioWhitelist", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Comma-separated list of captioning tracks (based on ISO 639-2 language code) that may not be selected from the \\*\\*Accessibility>Captioning track\\*\\* setting for the content. (Available since Roku OS 9.4) If a language is both blacklisted and whitelisted, the blacklisting takes precedence.", + "name": "CaptionBlacklist", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Comma-separated list of captioning tracks (based on ISO 639-2 language code) that may be selected from the \\*\\*Accessibility>Captioning track\\*\\* setting for the content.", + "name": "CaptionWhitelist", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "List of Category/Genre Names or Individual Category/Genre Name", + "name": "Categories", + "type": "roArray or String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "To use this field, create a child node and use a playlist (even though only one content item will be in the playlist). This field is updated only when \\*\\*contentIsPlayList\\*\\* is true. The \\*\\*URLFilter\\*\\*, \\*\\*Priority\\*\\*, and \\*\\*Weight\\*\\* attributes must be specified to apply these configurations.", + "name": "cdnConfig", + "type": "roArray of roAssociativeArrays" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "ClipEnd sets the clip end position. The unit of ClipEnd is seconds (Available since Roku OS 8.1).", + "name": "ClipEnd", + "type": "Float" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "ClipStart sets the clip start position of the playback. The unit of ClipStart is seconds (Available since Roku OS 8.1).", + "name": "ClipStart", + "type": "Float" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Boolean indicating if CC icon should be displayed", + "name": "ClosedCaptions", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "\\* \" \" \\* \"action\" \\* \"animated\" \\* \"black+white\" (black and white) \\* \"comedy\" \\* \"drama\" \\* \"music\" \\* \"music:lyrics\" \\* \"nature\" \\* \"news\" \\* \"podcast\" (audio only) \\* \"reality\" \\* \"sports\"", + "name": "contentClassifer", + "type": "string" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Although ContentType accepts type String, the return value is of type \\[roInt\\](https://developer.roku.com/docs/references/brightscript/components/roint.md. See table below.\n\n| Content Type | Return Value |\n| --- | --- |\n| audio | 5 |\n| episode | 4 |\n| movie | 1 |\n| not set or not supported | 0 |\n| season | 3 |\n| series | 2 |", + "name": "ContentType", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Description of content", + "name": "Description", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "List of Director Names", + "name": "Directors", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the PlayReady license acquisition URL, and additional custom request data, determined by the EncodingType attribute value specified: \\* when encodingType=\"PlayReadyLicenseAcquisitionUrl\", the EncodingKey attribute contains the PlayReady license acquisition URL", + "name": "EncodingKey", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the encoding scheme for PlayReady DRM, by setting to one of the following values: \\* \"PlayReadyLicenseAcquisitionUrl\" \\* \"PlayReadyLicenseAcquisitionAndChallenge\" Note, this is the same value that used to be specified directly in Content Metadata structure", + "name": "EncodingType", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Episode Number", + "name": "EpisodeNumber", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "BIF URL for FHD trick mode", + "name": "FHDBifUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "URL for FHD content artwork", + "name": "FHDPosterUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Filters out any video profile/codec level combinations that the specified hardware cannot play. The default value is false, in which case no filtering occurs. \\*\\*Note that this currently only works for DASH streams.\\*\\*", + "name": "filterCodecProfiles", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Controls whether query string parameters from initial DASH stream manifest requests are forward to subsequent segment download requests. The default value is set to false for backwards compatibility.", + "name": "ForwardDashQueryStringParams", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Controls whether query string parameters from initial HLS stream manifest requests are forward to subsequent segment download requests. The default value is set to true for backwards compatibility.", + "name": "ForwardQueryStringParams", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Specify the 1080p stream was encoded at 24 or 30 fps", + "name": "FrameRate", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Specify that this stream was encoded at 1080p resolution", + "name": "FullHD", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Overrides the `caption1NumLines` field for this section of the grid, allowing different sections to display different caption layouts. If not specified, the value of the `caption1NumLines` field is used.", + "name": "GridCaption1NumLines", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Overrides the `caption2NumLines` field for this section of the grid, allowing different sections to display different caption layouts. If not specified, the value of the `caption2NumLines` field is used.", + "name": "GridCaption1NumLines", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "When the fixedLayout field is set to true, this specifies how many rows the grid item occupies. If not specified, the default value of 1 is used.\nFor example, if a grid item is to occupy the the third, fourth and fifth rows, Y would be set to 2 and H would be set to 3.", + "name": "H", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roListScreen: URL for the HD background image", + "name": "HDBackgroundImageUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "BIF URL for HD trick mode", + "name": "HDBifUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Boolean indicating if HD branding should be displayed", + "name": "HDBranded", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the item poster when the screen resolution is set to HD. HDGRIDPOSTERURL is used if non-empty. HDPOSTERURL is used otherwise. See: PosterGrid", + "name": "HDGridPosterUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the icon to be displayed to the left of the list item label when the list item is focused", + "name": "HDListItemIconSelectedURL", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the icon to be displayed to the left of the list item label when the list item is not focused", + "name": "HDListItemIconURL", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "URL for HD content artwork", + "name": "HDPosterUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the item poster when the screen resolution is set to HD. HDGRIDPOSTERURL is used if non-empty. HDPOSTERURL is used otherwise. See: PosterGrid", + "name": "HDPosterUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the channel logo or for an icon that appears beside the program title. See: TimeGrid", + "name": "HDSmallIconUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "When set to true, the default, the list item displays the checkbox icon, reflecting the item's current selection state. When set to false, no checkbox icon is displayed, allowing the list to contain a mix of checkbox and regular list items.", + "name": "HideIcon", + "type": "boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "When set to true the media player will not stop playback when it runs into a streaming related error for this content. Instead, it will skip to the next item in the content list. If this was the last item in the content list the media player will send a regular completion event (like isFullResult). Channels are still notified of any errors via an isRequestFailed notification but a new attribute in the event’s GetInfo object tells the channel the error was ignored. See the changes related to isRequestFailed for more information. The default value is false.", + "name": "IgnoreStreamErrors", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Boolean indicating if content is HD", + "name": "IsHD", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Movie Length in Seconds; Length zero displays at 0m, Length not set will not display", + "name": "Length", + "type": "Float" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Optional flag indicating video is live. Replaces time remaining in progress bar to display \"Live\". Default is false", + "name": "Live", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Allows a channel to customize Media Player behavior on live streams when playing in the earliest part of a DVR buffer. The stream remains paused even though it is playing in the earliest part of the buffer of a live stream when the value of the attribute is set to \"pause.\" This enables the Roku OS to distinguish between live streams and live streams that eventually transition to video on demand. The possible values of this attribute are \"resume\", \"stop\", \"pause\", with resume being the default value. \\*\\*Currently, this attribute will work only with Smooth and Dash streams.\\*\\* (Available since Roku OS 8.1)", + "name": "LiveBoundsPauseBehavior", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth less than this maximum bandwidth. Units are kbps", + "name": "MaxBandwidth", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth higher than this minimum bandwidth. Units are kbps. By default Wowza servers set streams to 64 kbs, so you might want to set this parameter to something smaller than 64 when first testing Wowza streams. You will eventually want to specify the Wowza bitrates with a smil file (Please see the encoding guide)", + "name": "MinBandwidth", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Number of episodes for a \"season\" or \"series\" contentType", + "name": "NumEpisodes", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "PlayStart defines the start position of the content, in seconds. The player is not allowed to move to a position prior to this point. Any seek operation prior to this point will be clipped to PlayStart. Channels can use PlayStart and PlayDuration to split one content piece into multiple clips and insert these clips with other content (typically advertisements) into one content list. Starting from Roku OS 8.0, content metadata supports negative PlayStart values. This feature allows the media players to start playbacks distanced from the edge of the live stream", + "name": "PlayStart", + "type": "Float" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the audio codec that should be used during playback. The Media Player will select and report to the channel only those audio renditions that are encoded with the specified codec. Renditions that are encoded with a different codec are ignored. Possible values of this attribute are \"aac\", \"ac3\" and \"eac3\". (Available since Roku OS 9.0)", + "name": "PreferredAudioCodec", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Selects an icon to be displayed for the corresponding MPAA or TV rating, that is, the value will display as an icon artwork. See \\[Rating Attribute Icons\\](/docs/developer-program/getting-started/architecture/content-metadata.md#rating-attribute-icons) for a list of the acceptable values and the corresponding icon.", + "name": "Rating", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Formatted Date String", + "name": "ReleaseDate", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roListScreen: URL for the SD background image", + "name": "SDBackgroundImageUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "BIF URL for SD trick mode", + "name": "SDBifUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the item poster when the screen resolution is set to HD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid", + "name": "SDGridPosterUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "URL for SD content artwork", + "name": "SDPosterUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the item poster when the screen resolution is set to SD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid", + "name": "SDPosterUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The image file for the item poster when the screen resolution is set to SD. SDGRIDPOSTERURL is used if non-empty. SDPOSTERURL is used otherwise. See: PosterGrid", + "name": "SDPosterUrl", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Secondary title for the video content", + "name": "SecondaryTitle", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Line 1 of Poster Screen Description", + "name": "ShortDescriptionLine1", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The text for the first grid item caption.", + "name": "ShortDescriptionLine1", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Line 2 of Poster Screen Description", + "name": "ShortDescriptionLine2", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "The text for the second grid item caption.", + "name": "ShortDescriptionLine2", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the star rating to display as red star icon artwork, as a number from 1 to 100: \\* 20 displays one star \\* 40 displays two stars \\* 60 displays three stars \\* 80 displays four stars \\* 100 displays five stars Numbers not divisible by 20 are displayed as a fractional star (A number of 30 will display one and a half stars)", + "name": "StarRating", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Supported by roVideoPlayer and roVideoScreen, but not the Roku Scene Graph Video node. For the Video node, use the top level url, streamformat, etc. attributes. The exception is cases where you don't have adaptive streams (typically MP4) and need to specify different bitrate variants separately. For this use case use the Streams attribute. roAssociativeArray that has parameters representing the stream settings that were set as individual roArrays in previous firmware revisions. The old method is still supported and descriptions of the parameters can be found under those content-meta data entries. For url please see StreamUrls, for quality it is now a Boolean that is true for HD quality.\n\n| Key | Type |\n| --- | --- |\n| url | String |\n| stickyredirects | Boolean |\n| quality | Boolean |\n| contentid | String |\n| bitrate | Integer |", + "name": "Stream", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Array of bitrates in kbps for content streams used. Setting stream bitrates using this value is recommended for non-adaptive video (such as MP4 progressive download) only. \\*\\*Must be used in conjunction with StreamUrls and StreamQualities\\*\\*", + "name": "StreamBitrates", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "array of strings values logged in Roku logs to identify stream and bitrate played", + "name": "StreamContentIDs", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Type of content \\* Type of content: \\* Default: H.264/AAC in .mp4 Container \\* Valid values: \\* \"mp4\" (mp4 will also accept .mov and .m4v files) \\* \"wma\" (deprecated) \\* \"mp3\" \\* \"hls\" -\"ism\" (smooth streaming) \\* \"dash\" (MPEG-DASH) \\* \"mkv\", \"mka\", \"mks\" \\* Deprecated: \\* \"wmv\"", + "name": "StreamFormat", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Array of Strings quality indicators identifying a stream as \"SD\" or \"HD\". \\*\\*Must be used in conjunction with StreamBitrates and StreamUrls\\*\\*", + "name": "StreamQualities", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Used by roVideoPlayer and roVideoScreen to specify the content metadata for a set of fixed bitrate streams. Each array item specifies the URL, bitrate, etc. for one stream variant. Setting stream content metadata using the Streams value is recommended for non-adaptive video (such as MP4 progressive download) only. For adaptive streaming, use the Stream metadata value.\n\n| Key | Type |\n| --- | --- |\n| url | String |\n| stickyredirects | Boolean |\n| quality | Boolean |\n| contentid | String |\n| bitrate | Integer |", + "name": "Streams", + "type": "roArray of roAssociativeArrays" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Optional. Default is 0. The offset into the stream which is considered the beginning of playback. Time in seconds.", + "name": "StreamStartTimeOffset", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Array of Boolean values indicating if the HTTP endpoint should be sticky and not subject to change on subsequent requests. Default is false", + "name": "StreamStickyHttpRedirects", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Array of URLs for content streams. Setting stream urls using this value is recommended for non-adaptive video (such as MP4 progressive download) only. \\*\\*Must be used in conjunction with StreamBitrates and StreamQualities\\*\\*", + "name": "StreamUrls", + "type": "roArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Theme metadata attribute that specifies the color to use when rendering subtitle text", + "name": "SubtitleColor", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the caption settings for content playback. TrackName sets the name of the caption track to render. This string is a concatenation of the track source and track id, separated by a \"/\". Valid track sources are: \"ism\", \"mkv\", \"eia608\" and \"dvb\". The track id must match the track identifier in the smooth or mkvmanifest. For example, if an mkvfile has a caption track called “english1” the TrackName to select this track is “mkv/english1”. When the track source is \"dvb\", the track id is the three-letter language code, with \"\\\\\\_sdh\" appended for subtitles for the deaf and hard of hearing. For example, \"dvb/eng\\\\\\_sdh\" are English subtitles for the deaf and hard of hearing and \"dvb/nor\" are normal Norwegian subtitles. For sideloaded caption tracks, the TrackName is the url from where the caption track can be downloaded.Sideloaded caption formats can include srt,ttml, anddfxp. Specifying eia608/1 will trigger the Roku OS to search for embedded 608/708 captions in the stream. In the 8.0 Roku OS, automatic track selection based on a preferred caption language setting is introduced. Omit setting a URL here to avoid interfering with the automatic track selection. It is sufficient to add the URLs to SubtitleTracks", + "name": "SubtitleConfig", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "SubtitleTracks sets the list of available caption tracks available to the stream. This list is added to the track list in the closed caption configuration dialog that is displayed during video playback when the user presses the Roku remote control \\\\\\* button. The captions from the selected track are then displayed on the screen. Language specifies the ISO 639.2B 3 character language code. This string is used to match the proper caption track with the audio language. Description specifies the text that will be shown for the corresponding track in the closed caption configuration dialog. For sideloaded caption tracks the TrackName is the URL from where the caption track can be downloaded. Sideloaded caption formats can include srt, ttml, and dfxp. The SubtitleTracks metadata is generally only used for side loaded captions. the Roku OS detects in-stream captions and thus specifying SubtitleTracks in this case is not necessary", + "name": "SubtitleTracks", + "type": "roArray of roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the path to an SRT or TTML formatted file used to render subtitles or closed captions, respectively. This is supported on roVideoScreen only. See \\[Closed Caption Support\\](/docs/developer-program/media-playback/closed-caption.md) for additional details", + "name": "SubtitleUrl", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen. Specify different stream switching algorithms to be used in HLS adaptive streaming. Only has an effect on HLS streams. \"full-adaptation\" uses measured bandwidth and buffer fullness to determine when to switch. This strategy requires that segments align across variants as the HLS spec requires. This is the new default", + "name": "SwitchingStrategy", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSlideShow displays this string on the bottom part of slide", + "name": "TextOverlayBody", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSlideShow displays this string in Upper Left corner of slide", + "name": "TextOverlayUL", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roSlideShow displays this string in Upper Right corner of slide", + "name": "TextOverlayUR", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Content title: movie title for films; episode title for TV series", + "name": "Title", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Season title for TV series", + "name": "TitleSeason", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \"ISM\") to specify. Set the TrackIDAudio field to the desired track's StreamIndex.Name attribute from the manifest file", + "name": "TrackIDAudio", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer: Used to specify a closed caption track in a video stream that supports 608/708 captions", + "name": "TrackIDSubtitle", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "roVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \"ISM\") to specify. Set the TrackIDVideo field to the desired track's StreamIndex.Name attribute from the manifest file", + "name": "TrackIDVideo", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Stream URL for Scene Graph Video node", + "name": "Url", + "type": "String" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Specifies the user star rating to display as yellow star icon artwork, as a number from 1 to 100: \\* 20 displays one star \\* 40 displays two stars \\* 60 displays three stars \\* 80 displays four stars \\* 100 displays five stars Does not display fractional stars for numbers not divisible by 20", + "name": "UserStarRating", + "type": "Integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "If set to true, hides the Scene Graph Video node trick play UI; If set to false (the default) shows the Scene Graph Video node trick play UI", + "name": "VideoDisableUI", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "When the fixedLayout field is set to true, this specifies how many columns the grid item occupies. If not specified, the default value of 1 is used.\nFor example, if the numColumns field were set to 3 and a grid item is to occupy the rightmost two columns, X would be set to 1 and W would be set to 2.", + "name": "W", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "Flag indicating if content has been watched", + "name": "Watched", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "not specified", + "description": "When the fixedLayout field is set to true, this specifies the first row of the grid occupied by this item, where 0 refers to the first row. Note that there can be more rows in the data than visible rows, where the number of visible rows is specified by the numRows field.\nFor example, if the data model contains enough data to fill 12 rows, X would be set to a value from 0 to 11.", + "name": "X", + "type": "integer" }, { "accessPermission": "READ_WRITE", - "default": "no default", - "description": "The URL of the library to be downloaded", - "name": "uri", - "type": "uri" + "default": "not specified", + "description": "When the fixedLayout field is set to true, this specifies the first column of the grid occupied by this item, where 0 refers to the first column. Note that the number of columns is always specified by the numColumns field, regardless of how many items are in the data model.\nFor example, if the numColumns field is set to 3, Y would be set to 0, 1 or 2.", + "name": "Y", + "type": "integer" } ], "interfaces": [], - "name": "ComponentLibrary", - "url": "https://developer.roku.com/docs/references/scenegraph/control-nodes/componentlibrary.md" - }, - "contentnode": { - "description": "Extends [**Node**](https://developer.roku.com/docs/references/scenegraph/node.md\n\nThe ContentNode class allows you to specify the data used to configure a node or component. Many nodes and components require a ContentNode as the specification of their content field in order to be properly configured. In general, lists, grids, and panels require a ContentNode for configuration. The data included in a ContentNode can be data such as the text for labels in the node or component, and the spacing between items in a list, grid, or panel, including data to create custom lists, grids, and panels. The reference information for every node or component that requires a ContentNode includes a section that details the requirements of the ContentNode for that node or component.\n\nContentNodes defined as the specification for a node or component content field are typically structured as one ContentNode parent node, with a hierarchy of child nodes that specify the actual data, and sections of data if needed. For example, a [LabelList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/labellist.md\"LabelList\") node can have several sections that divide the entire list, each with their own section heading, and specific items in that section of the list. The ContentNode for that LabelList node should have two levels of child ContentNodes, one level for the data to configure the list sections, and then another level of child ContentNodes for the data for each item in that list section.\n\nA ContentNode can also be used to specify the data for custom components with defined interfaces, and for nodes and components that require [Content Meta-Data](/docs/developer-program/getting-started/architecture/content-metadata.md \" Content Meta-Data\"). Also, you should use a ContentNode for complex structures of data for your application rather than associative arrays. ContentNode objects are passed by reference in the application, while associative array objects are copied. For large complex data structures, passing ContentNode objects is much quicker than passing the equivalent associative array object. You can use associative arrays for simpler data structures with just a few fixed members.\n\n> All of the attributes listed in [Content Meta-Data](/docs/developer-program/getting-started/architecture/content-metadata.md \"Content Meta-Data\") can be set as fields in a Content node. However, when creating a Content node, the fields themselves are not created until the valid attributes are set as fields, using either assignment (=), or set using [setField()](https://developer.roku.com/docs/references/brightscript/interfaces/ifsgnodefield.mdsetfieldfieldname-as-string-value-as-object-as-boolean \"setField\") or [setFields()](https://developer.roku.com/docs/references/brightscript/interfaces/ifsgnodefield.mdsetfieldsfields-as-object-as-boolean. \"setFields()\")", - "events": [], - "extends": { - "name": "Node", - "url": "https://developer.roku.com/docs/references/scenegraph/node.md" - }, - "fields": [], - "interfaces": [], "name": "ContentNode", "url": "https://developer.roku.com/docs/references/scenegraph/control-nodes/contentnode.md" }, @@ -1298,7 +2117,7 @@ "accessPermission": "READ_WRITE", "default": "false", "description": "Hides the keyboard's internal \\*\\*VoiceTextEditBox\\*\\*, and renders the keyboard's \\*\\*DynamicKeyGrid\\*\\* at the top of the node.", - "name": "hideTextBox _Available since Roku OS 11.0_", + "name": "hideTextBox", "type": "boolean" }, { @@ -1306,7 +2125,7 @@ "default": "The DynamicKeyGrid associated with the keyboard", "description": "The internal \\[DynamicKeyGrid node\\](https://developer.roku.com/docs/references/scenegraph/dynamic-voice-keyboard-nodes/dynamic-key-grid.md used by this DynamicKeyboardBase node. Do not set this field to null or to a different DynamicKeyGrid node; this field should be only used to access the fields of this node's internal DynamicKeyGrid node, such as the mode or horizWrapping fields.", "name": "keyGrid", - "type": "**DynamicKeyGrid node(/docs/references/scenegraph/dynamic-voice-keyboard-nodes/dynamic-key-grid.md)**" + "type": "**[DynamicKeyGrid node](/docs/references/scenegraph/dynamic-voice-keyboard-nodes/dynamic-key-grid.md)**" }, { "accessPermission": "READ_WRITE", @@ -1320,7 +2139,7 @@ "default": "The VoiceTextEditBox associated with the keyboard", "description": "The internal \\[VoiceTextEditBox node\\](https://developer.roku.com/docs/references/scenegraph/dynamic-voice-keyboard-nodes/voice-text-edit-box.md used by this DynamicKeyboardBase node. Do not set this field to null or to a different VoiceTextEditBox node; this field should be used only to access the fields of this node's internal VoiceTextEditBox node.", "name": "textEditBox", - "type": "**VoiceTextEditBox** node(/docs/references/scenegraph/dynamic-voice-keyboard-nodes/voice-text-edit-box.md)" + "type": "[**VoiceTextEditBox** node](/docs/references/scenegraph/dynamic-voice-keyboard-nodes/voice-text-edit-box.md)" } ], "interfaces": [], @@ -1339,14 +2158,14 @@ "accessPermission": "WRITE-ONLY", "default": "\"\"", "description": "Draws the key's label or icon with a disabled appearance and prevents the key from gaining focus. If the key has focus when it becomes disabled, the focus is automatically moved to an adjacent key that is not disabled (the key above the disabled key is checked first, then the key below, to the right, and then to the left). To disable/enable a key, set the respective field to the key's \\*\\*label\\*\\* or \\*\\*StrOut\\*\\* value as defined in the Key Definition File. For example, to disable the \"backspace\" key, which typically has a delete icon displayed on the keyboard, enter the following: m.keyboard.keyGrid.disableKey = \"backspace\". Multiple keys may be disabled at any time by setting the write-only \\*\\*disableKey\\*\\* field once for each key to be disabled.", - "name": "disableKey _Available since Roku OS 10.5_", + "name": "disableKey", "type": "string" }, { "accessPermission": "WRITE-ONLY", "default": "\"\"", "description": "Draws the key's label or icon with an enabled appearance and allows the key to gain focus. To disable/enable a key, set the respective field to the key's \\*\\*label\\*\\* or \\*\\*StrOut\\*\\* value as defined in the KDF file. For example, to enable the \"backspace\" key, which typically has a delete icon displayed on the keyboard, enter the following: m.keyboard.keyGrid.enableKey = \"backspace\". Multiple disabled keys may be re-enabled at any time by setting the write-only \\*\\*enableKey\\*\\* field once for each key to be enabled.", - "name": "enableKey _Available since Roku OS 10.5_", + "name": "enableKey", "type": "string" }, { @@ -1814,7 +2633,7 @@ "accessPermission": "READ_WRITE", "default": "false", "description": "Specifies whether to display the end or beginning of text that overflows its available width: \\* \\*\\*true\\*\\*. The end of the text is shown. For example, \"the quick brown fox jumps over the lazy dog\" would be truncated to \"...jumps over the lazy dog\". \\* \\*\\*false\\*\\*. The start of the text is shown (for example, \"the quick brown fox jumps...\").", - "name": "leadingEllipsis _Available since Roku OS 11.0_", + "name": "leadingEllipsis", "type": "Boolean" }, { @@ -3651,6 +4470,82 @@ "name": "PosterGrid", "url": "https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/postergrid.md" }, + "progressbar": { + "description": "Component that shows the progress of re-buffering, after video playback has started.", + "events": [], + "extends": { + "name": "Node", + "url": "https://developer.roku.com/docs/references/scenegraph/node.md" + }, + "fields": [ + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "A color to be blended with the graphical image specified in the `emptyBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "emptyBarBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "A 9-patch or ordinary PNG of the bar presenting the remaining work to be done. This is typically displayed on the right side of the track, and is blended with the color specified in the `emptyBarBlendColor` field, if set to a non-default value.", + "name": "emptyBarImageUri", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "This color will be blended with the graphical image specified in the `filledBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "filledBarBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "A 9-patch or ordinary PNG of the bar that represents the completed portion of the work represented by this ProgressBar node. This is typically displayed on the left side of the track. This will be blended with the color specified by the `filledBarBlendColor` field, if set to a non-default value.", + "name": "filledBarImageUri", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "Sets a custom width for an instance of the ProgressBar node.", + "name": "height", + "type": "float" + }, + { + "accessPermission": "READ_WRITE", + "default": "not sepcified", + "description": "A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.", + "name": "percentage", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "This color is blended with the graphical image specified by `trackImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "trackBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.", + "name": "trackImageUri", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "Sets a custom width for an instance of the ProgressBar node.", + "name": "width", + "type": "float" + } + ], + "interfaces": [], + "name": "TrickPlayBar", + "url": "https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields" + }, "progressdialog": { "description": "> Roku OS 10.0 introduced a new [StandardProgressDialog node](https://developer.roku.com/docs/references/scenegraph/standard-dialog-framework-nodes/standard-progress-dialog.md\"**Standard Progress Dialog**\"), which features updated graphics and color palette support. This enables developers to provide a consistent user experience across the progress dialogs in their channel. Developers should replace the legacy ProgressDialog nodes in their channel with the new [StandardProgressDialog nodes](https://developer.roku.com/docs/references/scenegraph/standard-dialog-framework-nodes/standard-progress-dialog.md22 \"**Standard Progress Dialog**\").\n> \n> To upgrade a legacy progress dialog to the standard version, prepend \"Standard\" to the node type. For example, change `progressdialog = createObject(\"roSGNode\", \"ProgressDialog\")` to `progressdialog = createObject(\"roSGNode\", \"StandardProgressDialog\")`.\n\nExtends [**Dialog**](https://developer.roku.com/docs/references/scenegraph/dialog-nodes/dialog.md\"**Dialog**\")\n\nThe ProgressDialog node class is a special type of Dialog node that includes the dialog title region and a spinning icon as the body of the dialog. The ProgressDialog node class uses a BusySpinner node to display the spinning icon.\n\nThe message, bulleted text, graphic, and button regions of the dialog should all be empty. If those dialog regions are not empty, the layout of the dialog will likely not look correct.\n\n> Not all Roku Player hardware supports arbitrary rotations. In particular, some hardware only supports 90 degree rotation increments. In those cases, the icon will step through 90 degree, 180 degree, 270 degree and back to 0 degree rotations, rather than spin smoothly.", "events": [], @@ -3779,7 +4674,7 @@ "accessPermission": "READ_WRITE", "default": "0.0", "description": "Indicates which column of the currently focused row in a RowList component currently has focus. This field is typically used to implement a horizontal pagination mechanism for the currently focused row. If this value is set to 3.7, it means that item 3 occupies 30% of the currently focused row while item 4 occupies 70% of it. To maximize performance, the field should be kept to a minimum, as these scripts will run once during each render.", - "name": "currFocusColumn _Available since Roku OS 10.5_", + "name": "currFocusColumn", "type": "float" }, { @@ -5542,7 +6437,7 @@ "accessPermission": "READ_WRITE", "default": "false", "description": "Specifies whether to display the end or beginning of text that overflows its available width: \\* \\*\\*true\\*\\*. The end of the text is shown. For example, \"the quick brown fox jumps over the lazy dog\" would be truncated to \"...jumps over the lazy dog\". \\* \\*\\*false\\*\\*. The start of the text is shown (for example, \"the quick brown fox jumps...\").", - "name": "leadingEllipsis _Available since Roku OS 11.0_", + "name": "leadingEllipsis", "type": "Boolean" }, { @@ -5634,6 +6529,75 @@ "name": "Timer", "url": "https://developer.roku.com/docs/references/scenegraph/control-nodes/timer.md" }, + "trickplaybar": { + "description": "The visible TrickPlayBar node.", + "events": [], + "extends": { + "name": "Node", + "url": "https://developer.roku.com/docs/references/scenegraph/node.md" + }, + "fields": [ + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "This is blended with the marker for the current playback position. This is typically a small vertical bar displayed in the TrickPlayBar node when the user is fast-forwarding or rewinding through the video.", + "name": "currentTimeMarkerBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "This color will be blended with the graphical image specified in the `filledBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "filledBarBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "A 9-patch or ordinary PNG of the bar that represents the completed portion of the work represented by this ProgressBar node. This is typically displayed on the left side of the track. This will be blended with the color specified by the `filledBarBlendColor` field, if set to a non-default value.", + "name": "filledBarImageUri", + "type": "uri" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "The color of the trickplay progress bar to be blended with the `filledBarImageUri` for live linear streams.", + "name": "liveFilledBarBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "Sets the color of the text next to the trickPlayBar node indicating the time elapsed/remaining.", + "name": "textColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "Sets the blend color of the square image in the trickPlayBar node that shows the current position, with the current direction arrows or pause icon on top. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "thumbBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "0xFFFFFFFF", + "description": "This color is blended with the graphical image specified by `trackImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place.", + "name": "trackBlendColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"\"", + "description": "A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value.", + "name": "trackImageUri", + "type": "uri" + } + ], + "interfaces": [], + "name": "TrickPlayBar", + "url": "https://developer.roku.com/en-ca/docs/references/scenegraph/media-playback-nodes/video.md#ui-fields" + }, "vector2dfieldinterpolator": { "description": "Extends [**Node**](https://developer.roku.com/docs/references/scenegraph/node.md\n\nVector2DFieldInterpolator specifies a keyframe animation sequence to be applied to a pair Vector2D field of a node. Most typically, this is used to animate the (x,y) coordinates of a node's translation field.\n\nAll field interpolators include a set of key/keyValue pairs that define a keyframe of the animation. Field interpolators are generally used as children of an Animation node. As the animation progresses, it sets the fraction field of its field interpolators to a value between 0 and 1, indicating the percentage of the Animation's progress. The keyframes of the interpolator include a \"key\", the percentage where the keyframe should occur, and a \"keyValue\", the value that the field should have at that percentage.\n\nFor example, if a Vector2DFieldInterpolator had three keyframes, (0.0, \\[0.0, 0.0\\]), (0.4, \\[500.0, 0.0\\]) and (1.0, \\[500, 200.0\\]), then when the interpolator's fraction field was 0.0 (i.e. 0%), the field would be set to \\[0.0, 0.0\\]. When fraction was 0.4 (i.e. 40%), the field would be set to \\[500.0, 0.0\\]. When fraction was 1.0 (i.e. 100%), the field would be set to \\[500.0, 200.0\\].\n\nFor values of fraction between 0.0 and 0.4 (e.g. 0.2 or 20%), the field value is determined by linearly interpolating the keyValues for the first two keyframes. In this case, since the key of 0.2 is halfway between the key at 0.0 and the key at 0.4, the field would be set to \\[250.0, 0.0\\] (halfway between the point \\[0.0, 0.0\\] and \\[200.0, 0.0\\]. Similarly, when fraction is between the second and third keys (i.e. between 0.4 and 1.0), the field value is determined by linearly interpolating the keyValues of the second and third keyframes.\n\nFor this example, if the field being interpolated were the translation field of a Poster node parented to the Scene node, the Poster would originally be positioned with its top/left corner at the upper, left corner of the screen. As the animation proceeded from 0% to 40% complete, the Poster would slide horizontally to the right until it's top/left corner was at x=500.0, y=0.0. As the animation continued from 40% to 100% complete, the Poster would slide vertically down until its top/left corner was at x=500.0, y=200.0.\n\nIf the first keyframe has a key percentage greater than zero, then the field value will be equal to the keyValue of the first keyframe until fraction reaches the first keyframe's key percentage. Similarly, if the last keyframe has a key percentage less than one, the field value will be set to the keyValue of the last keyframe from when fraction equals the the last keyframe's key percentage and will not change as fraction increases from that value to 1.0.\n\n> While linearly interpolation is used to compute the keyValue's for fraction values between successive keys, non-linear easing functions may be applied to the fraction values computed by the Animation node, so the overall animation may vary in speed.", "events": [], @@ -5694,57 +6658,225 @@ "accessPermission": "WRITE", "default": "false", "description": "Indicates whether the \"STOP\" command is executed asynchronously (true) or synchronously (false). By default, the STOP command is executed synchronously, which blocks the UI thread. Enabling this field makes the STOP command non-blocking, which enables the video to be switched faster. When this field is enabled, the \\`state\\` field is set to \"stopping\" when the asynnchronous stop begins. The \\`state\\` field then changes to \"stopped\" once the stop has been completed. Any other media player component calls on the UI thread that require the Video node to be re-instantiated should be blocked until the asynnchronous stop has been completed (for example, updating the \\`control\\` field to \"Play\" or \"Prebuffer\", updating the \\`seek\\` field, or updating the \\`seekClip\\` field). This is because a video node in the \"stopping\" state is still using the underlying media player, which is not available at that time. As a result, performing these types of operations on a different video while in the \"stopping\" state may result in a playback failure.", - "name": "asyncStopSemantics _Available since Roku OS 12.5_", + "name": "asyncStopSemantics", + "type": "boolean" + }, + { + "accessPermission": "READ_ONLY", + "default": "", + "description": "In all other cases they shouldn't .Contains the format of the currently playing audio.\n\n| Value | Meaning |\n| --- | --- |\n| \"\" | No stream playing |\n| none | Stream contains no playable audio |\n| unknown | Stream contains unknown audio |\n| aac | ISO/IEC 14496-3, Advanced Audio Coding |\n| aac\\_adif | ISO/IEC 14496-3, Advanced Audio Coding, ADIF container |\n| aac\\_adts | ISO/IEC 14496-3, Advanced Audio Coding, ADTS container |\n| aac\\_latm | ISO/IEC 14496-3, Advanced Audio Coding, LATM container |\n| ac3 | Dolby Digital |\n| ac4 | Dolby Audio - AC-4 |\n| alac | Apple Lossless |\n| dts | DTS Coherent Acoustics |\n| eac3 | Dolby Digital Plus |\n| flac | Free Lossless Audio Codec |\n| flac | Free Lossless Audio Codec |\n| mat | Dolby Audio - TrueHD |\n| mp3 | ISO/IEC 11172-3, MPEG Audio Layer III |\n| pcm | linear PCM |\n| vorbis | Ogg Vorbis |\n| wma | Microsoft Windows Media Audio (sunset as of Roku OS 12.5) |\n| wmapro | Microsoft Windows Media Pro Audio (sunset as of Roku OS 12.5) |", + "name": "audioFormat", + "type": "string" + }, + { + "accessPermission": "WRITE_ONLY", + "default": "{ }", + "description": "The significance and priority order of the attributes and values for the audio tracks available in the video stream. > A language matching any country code does not match a track that specifies the same language but with no country code. Provide the attribute fields from highest to lowest significance (for example, if the \\*\\*language\\*\\* should take precedence over the \\*\\*description\\*\\*, list \\*\\*language\\*\\* first. For the audio track languages, provide the language code values from highest to lowest priority (for example, if English for the United States \\\\\\[\"en-US\"\\\\\\] has precedence over English for the United Kingdom \\\\\\[\"en-UK\"\\\\\\], list the language codes in the following order: \\\\\\[\"en-US\", \"en-UK\"\\\\\\].\n\n| Field | Type | Description |\n| --- | --- | --- |\n| values | roAssociativeArray | Specify values for the following audio track attributes. List the attributes from highest to lowest significance.
FieldTypeDescription
languagearray of StringsA list of (ISO-639)/country (ISO-3166) codes for the audio track. List the languages in priority order (highest to lowest).
descriptivearray of StringsA flag indicating whether descriptives exist for the video playing in the stream. This is equivalent to the HLS \"public.accessibility.describes-video\" characteristic.
|\n| overrideSystem | boolean | Specify whether to use the channel's preferences over the system preferences (true) or use the channel's preferences only when the system preferences do not match any of the available tracks (false), which allows the channel to provide additional rules in this case. The default value is false. |\n\n\\*\\*Example\\*\\* \\`\\`\\` video.audioSelectionPreferences = { values: \\[ { language: \\[\"en-US\", \"en-UK\", \"en\", \"en-\\*\"\\] }, { descriptive: \"false\" } \\], overrideSystem: true } \\`\\`\\` \\*\\*Explanation\\*\\* The audio language with the highest priority is \"en-US\". The next highest priority language is \"en-UK\", then \"en\" with no country code, and finally \"en\" with any country code.", + "name": "audioSelectionPreferences", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "", + "description": "The track identifier of the selected audio track. Reading this field will return the track identifier of the audio selected by the user. Writing this value will change the audio track. However, channels should not do this unless they are implementing their own track selection menu that users control. This is because the Roku OS selects the best track automatically based on preferred language setting on the device. See \\[Automatic audio track selection\\](#automatic-audio-track-selection) for more information.", + "name": "audioTrack", + "type": "string" + }, + { + "accessPermission": "READ_WRITE", + "default": "true", + "description": "Enables video content to automatically play after rebuffering. Setting this flag to false disables this default behavior.", + "name": "autoplayAfterSeek", "type": "boolean" }, { - "accessPermission": "WRITE_ONLY", - "default": "{ }", - "description": "The significance and priority order of the attributes and values for the audio tracks available in the video stream. > A language matching any country code does not match a track that specifies the same language but with no country code. Provide the attribute fields from highest to lowest significance (for example, if the \\*\\*language\\*\\* should take precedence over the \\*\\*description\\*\\*, list \\*\\*language\\*\\* first. For the audio track languages, provide the language code values from highest to lowest priority (for example, if English for the United States \\\\\\[\"en-US\"\\\\\\] has precedence over English for the United Kingdom \\\\\\[\"en-UK\"\\\\\\], list the language codes in the following order: \\\\\\[\"en-US\", \"en-UK\"\\\\\\].\n\n| Field | Type | Description |\n| --- | --- | --- |\n| values | roAssociativeArray | Specify values for the following audio track attributes. List the attributes from highest to lowest significance.
FieldTypeDescription
languagearray of StringsA list of (ISO-639)/country (ISO-3166) codes for the audio track. List the languages in priority order (highest to lowest).
descriptivearray of StringsA flag indicating whether descriptives exist for the video playing in the stream. This is equivalent to the HLS \"public.accessibility.describes-video\" characteristic.
|\n| overrideSystem | boolean | Specify whether to use the channel's preferences over the system preferences (true) or use the channel's preferences only when the system preferences do not match any of the available tracks (false), which allows the channel to provide additional rules in this case. The default value is false. |\n\n\\*\\*Example\\*\\* \\`\\`\\` video.audioSelectionPreferences = { values: \\[ { language: \\[\"en-US\", \"en-UK\", \"en\", \"en-\\*\"\\] }, { descriptive: \"false\" } \\], overrideSystem: true } \\`\\`\\` \\*\\*Explanation\\*\\* The audio language with the highest priority is \"en-US\". The next highest priority language is \"en-UK\", then \"en\" with no country code, and finally \"en\" with any country code.", - "name": "audioSelectionPreferences (_Available since Roku OS 12.5_)", - "type": "roAssociativeArray" + "accessPermission": "READ_ONLY", + "default": "[ ] empty array", + "description": "Each associative array has the following entries:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| Language | string | ISO 639-2 three-letter language code |\n| Name | string | Descriptive name of the audio track |\n| Track | string | The track identifier. The value of this field may be used to select the audio track. |\n| HasAccessibilityDescription _Available since Roku OS 13.0_ | boolean | HLS: represents \"public.accessibility.describes-video.\" DASH: Audio track contains a textual description (intended for audio synthesis) or an audio description describing a visual component. |\n| HasAccessibilityEAI _Available since Roku OS 13.0_ | boolean | DASH: Audio track contains an element for improved intelligibility of the dialogue \\[Enhanced Audio Intelligibility\\]. |\n\nThe field also retrieves audio description tracks which are typically seen on broadcast TV. An audio description track is mixed with the main audio track.", + "name": "availableAudioTracks", + "type": "array of associative arrays" + }, + { + "accessPermission": "READ_ONLY", + "default": "[ ] empty array", + "description": "The list of subtitle tracks available in the video stream. The array is initially populated with the tracks specified in the Content Meta-Data, and additional tracks are added if they are detected by the digital video player. Each associative array has the following entries:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| Description | string | Descriptive name of the subtitle track |\n| Language | string | ISO 639-2 three-letter language code |\n| TrackName | string | The track identifier. The value of this field may be used to select the subtitle track. |\n| HasAccessibilityDescription _Available since Roku OS 13.0_ | boolean | HLS: represents \"public.accessibility.describes-music-and-sound.\" |\n| HasAccessibilityCaption _Available since Roku OS 13.0_ | boolean | HLS: represents \"public.accessibility.transcribes-spoken-dialog.\" DASH: Subtitle track contains captions |\n| HasAccessibilitySign _Available since Roku OS 13.0_ | boolean | DASH: Subtitle track contains a sign-language interpretation of an audio component info. |", + "name": "availableSubtitleTracks", + "type": "array of associative arrays" + }, + { + "accessPermission": "READ_WRITE", + "default": "internal instance default", + "description": "Component that displays BIFs and allows navigation. The fields of this internal node are as follows:\n\n| Field | Type | Default | Use |\n| --- | --- | --- | --- |\n| frameBgBlendColor | color | 0xFFFFFFFF | A color to be blended with the image displayed behind individual BIF images displayed on the screen. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place. |\n| frameBgImageUri | uri | \"\" | The URI of an image to be displayed behind individual frames on the screen. The actual frame image is displayed opaquely on top of this background, so only the outer edges of this image are visible. Because of that, this background image typically appears as a border around the video frame. If the frameBgBlendColor field is set to a value other than the default, that color will be blended with the background image. |\n| getNearestFrame | time | invalid | **Write-Only** Requests the nearest BIF to the time specified. This would normally be an offset from the current playback position. The getNearestFrame request is passed to the BifCache which uses the getNearestFrame() method implemented on all BIF storage classes. Existing BifCache functionality is then used to retrieve the bitmap data and load it into the texture manager. |\n| nearestFrame | string | \"\" | **Read-Only** Contains the URI of the requested BIF. The returned URIs will be of the form 'memory://BIF_%d_%d'. These URIs can then be used directly in the 'uri' field of a Poster SGN (or similar). |", + "name": "bifDisplay", + "type": "BifDisplay node" + }, + { + "accessPermission": "READ_WRITE", + "default": "internal instance default", + "description": "Component that shows the progress of re-buffering, after video playback has started. The fields of this internal node are as follows:\n\n| Field | Type | Default | Use |\n| --- | --- | --- | --- |\n| width | float | system default | Sets a custom width for an instance of the ProgressBar node |\n| height | float | system default | Sets a custom height for an instance of the ProgressBar node |\n| emptyBarBlendColor | color | 0xFFFFFFFF | A color to be blended with the graphical image specified in the `emptyBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place. |\n| emptyBarImageUri | uri | \"\" | A 9-patch or ordinary PNG of the bar presenting the remaining work to be done. This is typically displayed on the right side of the track, and is blended with the color specified in the `emptyBarBlendColor` field, if set to a non-default value. |\n| filledBarBlendColor | color | 0xFFFFFFFF | This color will be blended with the graphical image specified in the `filledBarImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place. |\n| filledBarImageUri | uri | \"\" | A 9-patch or ordinary PNG of the bar that represents the completed portion of the work represented by this ProgressBar node. This is typically displayed on the left side of the track. This will be blended with the color specified by the `filledBarBlendColor` field, if set to a non-default value. |\n| trackBlendColor | color | 0xFFFFFFFF | This color is blended with the graphical image specified by `trackImageUri` field. The blending is performed by multiplying this value with each pixel in the image. If not changed from the default value, no blending will take place. |\n| trackImageUri | uri | \"\" | A 9-patch or ordinary PNG of the track of the progress bar, which surrounds the filled and empty bars. This will be blended with the color specified by the `trackBlendColor` field, if set to a non-default value. |\n| percentage | integer | top | The percentage of the work that is done. Setting this field controls the visual appearance of the ProgressBar node. |", + "name": "bufferingBar", + "type": "ProgressBar node" + }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Contains information about stream buffering progress and status. This field is valid only while buffering is in progress, both at stream startup or when re-buffering is required. Observers will be notified when any element of the array changes, and also when buffering is complete and the field itself becomes invalid. The array contains the following name - value pairs.\n\n| Value | Meaning |\n| --- | --- |\n| percentage | Percent buffering complete as an integer. |\n| isUnderrun | Boolean value indicating if a stream underrun occurred. |\n| prebufferDone (_Available since Roku OS 10.0_) | A boolean value that indicates whether the player has buffered enough data to allow the player to begin playing immediately should \"control\" be set to \"play.\" |\n| actualStart (_Available since Roku OS 10.0_) | A time value that is automatically set when prebufferDone becomes true, specifying the actual time from which playback will resume. This may vary from the time requested in the content node's playStart field, depending on the capabilities of the player and the seekMode setting. |\n\n\\> While it is possible to use the Video node seek field to specify the seek time, it is recommended that channels do the following: > > 1. Set the content node field playStart in seek-to-pause scenarios. > 2. In the video node, set \"control\" to \"prebuffer\". > 3. Wait for \"prebufferDone\" to become \"true\". > 4. Check \"actualStart\" (if desired). > 5. Set \"control\" to \"play\".", + "name": "bufferingStatus", + "type": "associative array" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "The color of the text displayed near the buffering bar defined by the \\`bufferingBar\\` field, when the buffering bar is visible. If this is 0, the system default color is used. To set a custom color, set this field to a value other than 0x0.", + "name": "bufferingTextColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "Allows channels to style closed captions. For any keys that are absent from the associative array, or for unexpected values, the Default value is assumed for that property. Following are the possible key names and values for this field:\n\n| Property | Possible Values |\n| --- | --- |\n| Text/Font | Default Serif Fixed Width Serif Proportional Sans Serif Fixed Width Sans Serif Proportional Casual Cursive Small Caps |\n| Text/Effect | Default None Raised Depressed Uniform Drop shadow (left) Drop shadow (right) |\n| Text/Size | Default Large Medium Small |\n| Text/Color | Default White Black Red Green Blue Yellow Magenta Cyan |\n| Text/Opacity | Default 25% 50% 75% 100% |\n| Background/Color | Default White Black Red Green Blue Yellow Magenta Cyan |\n| Background/Opacity | Default Off 25% 50% 75% 100% |\n| Window/Color | Default White Black Red Green Blue Yellow Magenta Cyan |\n| Window/Opacity | Default Off 25% 50% 75% 100% |", + "name": "captionStyle", + "type": "associative array" + }, + { + "accessPermission": "READ_WRITE", + "default": "0", + "description": "Sets the CGMS (Copy Guard Management System) on analog outputs to the desired level. The valid values are:\n\n| Value | Effect |\n| --- | --- |\n| 0 | No copy restriction |\n| 1 | Copy no more |\n| 2 | Copy once allowed |\n| 3 | No copying permitted |", + "name": "cgms", + "type": "integer" + }, + { + "accessPermission": "READ_ONLY", + "default": "0", + "description": "The clip ID of the currently playing track.", + "name": "clipId", + "type": "integer" + }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Information about the video stream that most recently completed playing, due to an error, user action, or end of the stream. The associative array consists of the same keys as for the \\`streaminfo\\` field, with one additional key, \\`isFullResult\\`, a Boolean type that, if true indicates the \\`stream\\` played to completion, or if false, was interrupted by an error or user action. This field is set prior to the \\`state\\` field being changed, so \\`state\\` field observer callback functions can assume that the associative array values are valid when the state field changes.", + "name": "completedStreamInfo", + "type": "associative array" + }, + { + "accessPermission": "READ_WRITE", + "default": "NULL", + "description": "The ContentNode with the \\[Content Meta-Data\\](/docs/developer-program/getting-started/architecture/content-metadata.md) for the video, or a video playlist (a sequence of videos) to be played. If a video playlist is to be played, the children of this ContentNode comprise the playlist, and each ContentNode child must have all attributes required to play that video. For example, if the videos \"A\" and \"B\" are to be played, three ContentNodes must be created: the parent ContentNode (which is largely ignored), one ContentNode child for \"A,\" and one ContentNode child for \"B.\" The parent node is set into this content field, and when video playback is started, all of its children will be played in sequence. Any changes made to the playlist after playback has started are ignored. See the \\`contentIsPlaylist\\` and \\`contentIndex\\` fields, for more information on playlists.", + "name": "content", + "type": "ContentNode" + }, + { + "accessPermission": "READ_ONLY", + "default": "false", + "description": "\\_Available since Roku OS 8.\\_ Determines whether the current content is blocked.", + "name": "contentBlocked", + "type": "Boolean" + }, + { + "accessPermission": "READ_ONLY", + "default": "-1", + "description": "The index of the video in the video playlist that is currently playing. Generally, you would only want to check this field if video playlists are enabled (by setting the \\`contentIsPlaylist\\` field to true), but it is set to 0 when a single video is playing, and video playlists are not enabled.", + "name": "contentIndex", + "type": "integer" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "If set to true, enables video playlists (a sequence of videos to be played). See the \\`content\\` and \\`contentIndex\\` field for more information on playlists.", + "name": "contentIsPlaylist", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "none", + "description": "Sets the desired play state for the video, such as starting or stopping the video play. Getting the value of this field returns the most recent value set, or none if no value has been set. To dynamically monitor the actual state of the video, see the \\`state\\` field. The play and stop commands to commence and discontinue playback should not be used to implement trick modes like rewind, or replay. For that use the \\`seek\\` field.\n\n| Option | Effect |\n| --- | --- |\n| none | No play state set |\n| play | Start video play |\n| stop | Stop video play |\n| pause | Pause video play |\n| resume | Resume video play after a pause |\n| replay | Replay video |\n| prebuffer | Starts buffering the video stream before the Video node actually begins playback. Only one video stream can be buffering in the application at any time. Setting the `control` field to `prebuffer` for another video stream after setting `prebuffer` for a previous video stream stops the buffering of the previous video stream. |\n| skipcontent | Skip the currently-playing content and begin playing the next content in the playlist. If the content is not a playlist, or if the current content is the end of the playlist, this will end playback. |", + "name": "control", + "type": "option string" + }, + { + "accessPermission": "READ_ONLY", + "default": "", + "description": "The track identifier of the audio being played. Reading this field will return the track that is being played, which may be different than the track being selected (for example, when the Roku media player cannot play a certain format). When the user has not selected an audio track, the platform will select a track based on the preferred audio language setting.", + "name": "currentAudioTrack", + "type": "String" + }, + { + "accessPermission": "READ_ONLY", + "default": "", + "description": "The identifier of the selected subtitle track. Subtitles may or may not be visible on the screen, depending upon the user's caption mode setting. Reading this field will return the identifier of the subtitle track that is playing. When the user has not selected a track, the Roku media player will select a track based on the preferred caption language system setting.", + "name": "currentSubtitleTrack", + "type": "string" + }, + { + "accessPermission": "READ_ONLY", + "default": "{}", + "description": "Provides the following video decoder statistics related to the start of video playback:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| renderCount | integer | The number of frames that have been rendered since playback was started. This value is incremented each time a new frame is rendered |\n| repeatCount | integer | The number of frames that have been repeated since playback was started.This value is incremented each time a new frame is not available in time and the current frame is rendered an additional frame period. |\n| frameDropCount | integer | The number of frames that have been dropped since playback was started.This value is incremented each time the presentation time of a decoded frame is too old to be rendered and the next frame is rendered instead. |\n| streamErrorCount | integer | The number of bitstream errors since playback was started.This value is incremented each time the decoder detects a bitstream error. |\n\nSet the \\*\\*enableDecoderStats\\*\\* field to true to enable updates to this field.", + "name": "decoderStats", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "Set this to true to suppress the screensaver. This is typically used to suppress the screensaver when playing audio-only streams.", + "name": "disableScreenSaver", + "type": "Boolean" + }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Information about the video segment that was just downloaded. This is only meaningful for segmented video transports, such as DASH and HLS. The associative array has the following entries:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| Status | integer | Status of the download: 0 = success, nonzero = error |\n| SegSequence | integer | Stream segment sequence number |\n| SegUrl | string | Stream segment URL (i.e., .ts file for HLS, stream fragment URL for smooth) |\n| DownloadDuration | integer | Amount of time spent downloading the segment, in milliseconds |\n| SegSize | integer | Segment size, in bytes |\n| SegType | integer | Type of data in the segment: 1=audio, 2=video, 3=captions, 0=mux |\n| BitrateBPS | integer | Bitrate of the segment, in bits per second |\n| SegStart | time | The start time of the segment from the start of the video, specified in seconds |\n| SegDuration | string | The duration of the segment in milliseconds. |\n| Path | string | A path indicating the Period, AdaptationSet and Representation that is played. This is in UNIX directory notation as: /// |\n| Width | integer | For video segments, the width of the encoded video picture |\n| Height | integer | For video segments, the height of the encoded video picture |\n| HdrMode | Indicates the HDR format of the content, which may be one of the following values: * 0: UNKNOWN * 1: NONE (SDR) * 2: HDR10 * 3: DOLBY\\_VISION * 4: HLG10 * 5: HDR10\\_PLUS * 6: SL\\_HDR2 | |\n| Latency | The time, in milliseconds, between the current live edge (or most recent available media segment on the CDN) and the segment currently being played. | |", + "name": "downloadedSegment", + "type": "associative array" + }, + { + "accessPermission": "READ_ONLY", + "default": "0", + "description": "The duration of the video being played, specified in seconds. This becomes valid when playback begins and may change if the video is dynamic content, such as a live event.", + "name": "duration", + "type": "time" }, { "accessPermission": "READ_WRITE", - "default": "NULL", - "description": "The ContentNode with the \\[Content Meta-Data\\](/docs/developer-program/getting-started/architecture/content-metadata.md) for the video, or a video playlist (a sequence of videos) to be played. If a video playlist is to be played, the children of this ContentNode comprise the playlist, and each ContentNode child must have all attributes required to play that video. For example, if the videos \"A\" and \"B\" are to be played, three ContentNodes must be created: the parent ContentNode (which is largely ignored), one ContentNode child for \"A,\" and one ContentNode child for \"B.\" The parent node is set into this content field, and when video playback is started, all of its children will be played in sequence. Any changes made to the playlist after playback has started are ignored. See the \\`contentIsPlaylist\\` and \\`contentIndex\\` fields, for more information on playlists.", - "name": "content", - "type": "ContentNode" + "default": "false", + "description": "Enables updates to the \\*\\*decoderStats\\*\\* field.", + "name": "enableDecoderStats", + "type": "boolean" }, { - "accessPermission": "READ_ONLY", - "default": "-1", - "description": "The index of the video in the video playlist that is currently playing. Generally, you would only want to check this field if video playlists are enabled (by setting the \\`contentIsPlaylist\\` field to true), but it is set to 0 when a single video is playing, and video playlists are not enabled.", - "name": "contentIndex", - "type": "integer" + "accessPermission": "READ_WRITE", + "default": "False", + "description": "Enables the scrubbing of the trickplay bar during the availability window of live linear streams.", + "name": "enableLiveAvailabilityWindow", + "type": "Boolean" }, { "accessPermission": "READ_WRITE", "default": "false", - "description": "If set to true, enables video playlists (a sequence of videos to be played). See the \\`content\\` and \\`contentIndex\\` field for more information on playlists.", - "name": "contentIsPlaylist", + "description": "Set this to true to allow the screensaver to activate even if video is playing, as long as that video does not cover 50% or more of the screen. Set to false to block the screensaver activating if any video is playing. Note that this field has no effect when the video node plays audio only streams. For screensaver control with audio only streams, use the disableScreenSaver field.", + "name": "enableScreenSaverWhilePlaying", "type": "Boolean" }, { "accessPermission": "READ_WRITE", - "default": "none", - "description": "Sets the desired play state for the video, such as starting or stopping the video play. Getting the value of this field returns the most recent value set, or none if no value has been set. To dynamically monitor the actual state of the video, see the \\`state\\` field. The play and stop commands to commence and discontinue playback should not be used to implement trick modes like rewind, or replay. For that use the \\`seek\\` field.\n\n| Option | Effect |\n| --- | --- |\n| none | No play state set |\n| play | Start video play |\n| stop | Stop video play |\n| pause | Pause video play |\n| resume | Resume video play after a pause |\n| replay | Replay video |\n| prebuffer | Starts buffering the video stream before the Video node actually begins playback. Only one video stream can be buffering in the application at any time. Setting the `control` field to `prebuffer` for another video stream after setting `prebuffer` for a previous video stream stops the buffering of the previous video stream. |\n| skipcontent | Skip the currently-playing content and begin playing the next content in the playlist. If the content is not a playlist, or if the current content is the end of the playlist, this will end playback. |", - "name": "control", - "type": "option string" + "default": "False", + "description": "Enables the \\*\\*thumbnailTiles\\*\\* field to be set and updated in the case of live HLS and DASH streams, which contain thumbnails as the thumbnails become available. By default and when this is set to false, the \\*\\*thumbnailTiles\\*\\* field is not written during live streams to maintain backwards compatibility with older applications and to avoid performance or memory issues. This is becuase they might not be expecting constant updates to the \\*\\*thumbnailTiles\\*\\* field if they were written to handle VOD streams, which rarely update the \\*\\*thumbnailTiles\\*\\* field.", + "name": "enableThumbnailTilesDuringLive", + "type": "Boolean" }, { - "accessPermission": "READ_ONLY", - "default": "{}", - "description": "Provides the following video decoder statistics related to the start of video playback:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| renderCount | integer | The number of frames that have been rendered since playback was started. This value is incremented each time a new frame is rendered |\n| repeatCount | integer | The number of frames that have been repeated since playback was started.This value is incremented each time a new frame is not available in time and the current frame is rendered an additional frame period. |\n| frameDropCount | integer | The number of frames that have been dropped since playback was started.This value is incremented each time the presentation time of a decoded frame is too old to be rendered and the next frame is rendered instead. |\n| streamErrorCount | integer | The number of bitstream errors since playback was started.This value is incremented each time the decoder detects a bitstream error. |\n\nSet the \\*\\*enableDecoderStats\\*\\* field to true to enable updates to this field.", - "name": "decoderStats (_Available since Roku OS 11.0_)", - "type": "roAssociativeArray" + "accessPermission": "READ_WRITE", + "default": "true", + "description": "\\*\\*Controls whether trickplay is allowed during playback. When set to false the user trickplay buttons on the remote will have no effect. This only applies when enableUI is set to true.\\*\\*", + "name": "enableTrickPlay", + "type": "Boolean" }, { "accessPermission": "READ_WRITE", - "default": "false", - "description": "Enables updates to the \\*\\*decoderStats\\*\\* field.", - "name": "enableDecoderStats (_Available since Roku OS 11.0_)", - "type": "boolean" + "default": "true", + "description": "If set to true (the default), the entire Video node user interface (such as ProgressBar and TrickPlayBar nodes, and BIF navigation) appear in response to stream events and remote control key presses. If set to false, most of the Video node user interface will not be shown, and the application is expected to implement the UI. The one exception is the closed-caption dialog, which always appears when the video is playing fullscreen (either full height or full width) and the user presses the Options (\\\\\\*) button. When using the \\[Roku Advertising Framework (RAF)\\](/docs/developer-program/advertising/roku-advertising-framework.md), the RAF library may temporarily set this field to false while playing ads.", + "name": "enableUI", + "type": "Boolean" }, { "accessPermission": "READ_ONLY", @@ -5774,6 +6906,20 @@ "name": "errorStr", "type": "string" }, + { + "accessPermission": "READ_WRITE", + "default": "Off", + "description": "Sets the value of the global Roku closed-caption mode. This can be used to allow the user or the application to change the closed-caption mode in an application just before or during video playback. The possible options are:\n\n| Option | Effect |\n| --- | --- |\n| \"Off\" | Turns the global Roku closed-caption mode off. |\n| \"On\" | Turns the global Roku closed-caption mode on. |\n| \"Instant replay\" | Sets the global Roku closed-caption setting to display captions only during instant replay. |\n| \"When mute\" | Sets the global Roku closed-caption setting to display captions only when the volume is muted. (This only applies to Roku TVs.) |\n\nThe channel should set the \\`subtitleTrack\\` field regardless of the selected Caption Mode.", + "name": "globalCaptionMode", + "type": "option string" + }, + { + "accessPermission": "READ_WRITE", + "default": "0.0", + "description": "Sets the height of the video play window in pixels. If set to 0.0 (the default), the video play window is set to the height of the entire display screen.", + "name": "height", + "type": "float" + }, { "accessPermission": "READ_ONL", "default": "", @@ -5781,6 +6927,27 @@ "name": "licenseStatus", "type": "roAssociativeArray" }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "If set to true, the video or video playlist (if the \\`contentIsPlaylist\\` field is set to true to enable video playlists) will be restarted from the beginning after the end is reached.", + "name": "loop", + "type": "Boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "[0,0]", + "description": "Sets the max resolution required by your video. Video decode memory is a shared resource with OpenGL texture memory. The Brightscript 2D APIs are implemented using OpenGL texture memory on Roku models that support the Open GL APIs (see \\[Hardware specifications\\](/docs/specs/hardware.md) for a list of these models). On models that do not support Open GL APIs, this field exists for API compatibility but has no effect on actual memory allocations. Video decode memory allocation is based on a resolution of 1920x1080 or 1280x720 as the maximum supported resolution for a particular Roku model (see \\[Hardware specifications\\](/docs/specs/hardware.md) for a list of these models). This field enables applications that want to use both the 2D APIs and video playback with a lower resolution than 1080p. Without this field, these applications are likely to not have enough memory for either video playback or UI rendering. If width is 0 (the default), it is unlimited. If height is 0 (the default), it is unlimited.", + "name": "MaxVideoDecodeResolution", + "type": "vector2d (width, height)" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "Set to true to mute the audio of the video currently playing in the Video node. Set to false to restore audio.", + "name": "mute", + "type": "Boolean" + }, { "accessPermission": "READ_WRITE", "default": "-1", @@ -5788,60 +6955,102 @@ "name": "nextContentIndex", "type": "integer" }, + { + "accessPermission": "READ_WRITE", + "default": "0.5", + "description": "The interval between notifications to observers of the position field, specified as the number of seconds. If the value is 0, no notifications are delivered. This value may be read or modified at any time.", + "name": "notificationInterval", + "type": "time" + }, + { + "accessPermission": "READ_ONLY", + "default": "0", + "description": "The ending position of the video buffered when paused. This field is only valid for live video.", + "name": "pauseBufferEnd", + "type": "time" + }, + { + "accessPermission": "READ_ONLY", + "default": "false", + "description": "Indicates that the video buffer was not able to save all video since being paused. This field is only valid for live video.", + "name": "pauseBufferOverflow", + "type": "Boolean" + }, + { + "accessPermission": "READ_ONLY", + "default": "0", + "description": "The current presentation position of the video buffered when paused. This field is only valid for live video.", + "name": "pauseBufferPosition", + "type": "time" + }, + { + "accessPermission": "READ_ONLY", + "default": "0", + "description": "The beginning position of the video buffered when paused. This field is only valid for live video.", + "name": "pauseBufferStart", + "type": "time" + }, + { + "accessPermission": "READ_WRITE", + "default": "-", + "description": "The visible pivot node. This is a generic renderable node that can be used to display any component. This node is only displayed when video is paused.", + "name": "pivotNode", + "type": "renderable node" + }, { "accessPermission": "READ_WRITE", "default": "0", "description": "The index of the button that has focus in the \\*\\*playbackActionButtons\\*\\* field.", - "name": "playbackActionButtonFocused (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonFocused", "type": "integer" }, { "accessPermission": "WRITE", "default": "OX121212FF", "description": "Specifies the color of the button label text when the button has key focus.", - "name": "playbackActionButtonFocusedTextColor (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonFocusedTextColor", "type": "Color" }, { "accessPermission": "WRITE", "default": "SmallBoldSystemFont", "description": "Specifies the font of the button label when the button has key focus.", - "name": "playbackActionButtonFocusedTextFont (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonFocusedTextFont", "type": "Font" }, { "accessPermission": "WRITE", "default": "-", "description": "Specifies the button background color when the button has key focus.", - "name": "playbackActionButtonFocusIndicatorBlendColor (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonFocusIndicatorBlendColor", "type": "Color" }, { "accessPermission": "READ_WRITE", "default": "[]", "description": "Component that shows the buttons and other specified UI elements on the pause screen at the start of playback. Each element in the array has following fields:\n\n| Field | Type | Default | Description |\n| --- | --- | --- | --- |\n| text | string | system default | Text for the button label |\n| icon | uri | \"\" | A 9-patch or PNG of the icon to be displayed when the button does not have. |\n| focusIcon | uri | \"\" | A 9-patch or PNG of the icon to be displayed when the button has focus. |\n| buttonIsDisabled | Boolean | false | Controls whether the button is disabled (true) or enabled (false). A disabled button is skipped and does not have focus while the user navigates the different playback action buttons with the directional pad on the Roku remote control. |", - "name": "playbackActionButtons (_Available since Roku OS 11.5_)", + "name": "playbackActionButtons", "type": "roArray of roAssociativeArrays" }, { "accessPermission": "READ_WRITE", "default": "0", "description": "The index of the button that is selected in the \\*\\*playbackActionButtons\\*\\* field.", - "name": "playbackActionButtonSelected (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonSelected", "type": "integer" }, { "accessPermission": "WRITE", "default": "0xEFEFEFFF", "description": "Specifies the color of the button label text when the button does not have key focus.", - "name": "playbackActionButtonUnfocusedTextColor (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonUnfocusedTextColor", "type": "Color" }, { "accessPermission": "WRITE", "default": "SmallSystemFont", "description": "Specifies the font of the button label when the button does not have key focus.", - "name": "playbackActionButtonUnfocusedTextFont (_Available since Roku OS 11.5_)", + "name": "playbackActionButtonUnfocusedTextFont", "type": "Font" }, { @@ -5851,6 +7060,55 @@ "name": "playStartInfo", "type": "roAssociativeArray" }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Time of the current position in the stream. Either UTC time or elapsed since start of stream depending on content type. As of Roku OS 9.3, when the video is paused, the position is recorded for that pause event. This means that playing, pausing, and resuming a video generates three separate positions.", + "name": "position", + "type": "time" + }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Contains the following fields that provide information about the last rendered video and audio samples.\n\n| Field | Type | Default | Access Permission | Description |\n| --- | --- | --- | --- | --- |\n| audio | double | invalid | READ\\_ONLY | Position of the last rendered audio sample, specified in seconds |\n| clip\\_id | integer | invalid | READ\\_ONLY | The unique ID of the clip |\n| epoch | integer | invalid | READ\\_ONLY | 0 means positions are relative to videoStart; 1 means that positions are utc |\n| video | double | invalid | READ\\_ONLY | Position of the last rendered video sample, specified in seconds |", + "name": "positionInfo", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "internal instance default", + "description": "Component that shows the progress of initial retrieving of the video, prior to starting playback. The fields of this internal node are the same as for the \\`bufferingBar\\` field, which are the fields of the internal ProgressBar node.", + "name": "retrievingBar", + "type": "ProgressBar node" + }, + { + "accessPermission": "READ_WRITE", + "default": "system default", + "description": "The color of the text displayed near the retrieving bar, when the retrieving bar defined in the \\`retrievingBar\\` field is visible. If this is 0, the system default color is used. To set a custom color, set this field to a value other than 0x0.", + "name": "retrievingTextColor", + "type": "color" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "Enables apps to continuously play video when the audio track is switched. This feature currently supports HLS only. \\* \\*\\*true\\*\\*: Continues video playback when the audio track changes (provided that HLS is being used and the audio format of the new audio track is the same as the original one). In this case, a brief period of no audio may occur while the audio tracks are switched. \\* \\*\\*false\\*\\*: Pauses video playback for approximately 1 second when the audio track changes (default behavior). In this case, a black screen and/or buffering appears while the audio tracks are switched. To enable this feature, you must set this field before sending any command to the Video node. This field may not be changed during video playback.", + "name": "seamlessAudioTrackSelection", + "type": "Boolean" + }, + { + "accessPermission": "WRITE_ONLY", + "default": "invalid", + "description": "Sets the current position in the video. The value is the number seconds from the beginning of the stream, specified as a double.", + "name": "seek", + "type": "time" + }, + { + "accessPermission": "READ_WRITE", + "default": "\"default\"", + "description": "Determines the desired level of accuracy for seek behavior:\n\n| Value | Meaning |\n| --- | --- |\n| default | Seek to the closest sync frame (segment, or I-frame of a multi-frame segment) that is earlier than the requested seek time. |\n| accurate | Seek to the exact time requested if platform support (video decoder step function) is available. |", + "name": "seekMode", + "type": "string" + }, { "accessPermission": "READ_ONLY", "default": "none", @@ -5858,12 +7116,110 @@ "name": "state", "type": "value string" }, + { + "accessPermission": "READ_ONLY", + "default": "invalid", + "description": "Information about the video stream that is currently playing or buffering.\n\n| Key | Type | Value |\n| --- | --- | --- |\n| isUnderrun | Boolean | If true, the stream was downloaded due to an underrun |\n| isResume | Boolean | If true, playback was resumed after trickplay |\n| measuredBItrate | Integer | The measured bitrate (bps) of the network when the stream was selected |\n| streamBitrate | Integer | The bitrate of the stream |\n| streamUrl | URI | The URL of the stream |", + "name": "streamInfo", + "type": "associative array" + }, + { + "accessPermission": "READ_ONLY", + "default": "{ }", + "description": "Information about the video segment that is currently streaming. This is only meaningful for segmented video transports, such as DASH and HLS. The associative array has the following entries:\n\n| Key | Type | Value |\n| --- | --- | --- |\n| segBitrateBps | integer | Bitrate of the segment in bits per second |\n| segSequence | integer | The sequence number of the segment in the video |\n| segStart | time | The start time of the segment from the start of the video, specified in seconds |\n| segUrl | string | URL of the segment |\n| Path | string | A path indicating the Period, AdaptationSet and Representation that is played. This is in UNIX directory notation as: /// |\n| Width | integer | For video segments, the width of the encoded video picture |\n| Height | integer | For video segments, the height of the encoded video picture |", + "name": "streamingSegment", + "type": "associative array" + }, { "accessPermission": "WRITE_ONLY", "default": "{ }", "description": "The significance and priority order of the attributes and values for the subtitle tracks available in the video stream. Provide the attribute fields from highest to lowest significance (for example, if \\*\\*language\\*\\* should take precedence over all other attributes, list it first). For the subtitle track languages, provide the language codes from highest to lowest priority (for example, if Spanish for Latin America and the Caribbean \\\\\\[\"es-419\"\\\\\\] has precedence over Spanish \\\\\\[\"es\"\\\\\\], list the language codes in the following order: \\\\\\[\"es-419\", \"es\"\\\\\\].\n\n| Field | Type | Description |\n| --- | --- | --- |\n| values | roAssociativeArray | Specify values for the following subtitle track attributes. List the attributes from highest to lowest significance.
FieldTypeDescription
languagearray of StringsA list of language (ISO-639)/country (ISO-3166) codes for the subtitles. List the language codes in priority order (highest to lowest).
captionarray of StringsA flag indicating whether captions exist for the video stream. This is equivalent to the HLS \"public.accessibility.transcribes-spoken-dialog\" characteristic.
descriptivearray of StringsA flag indicating whether descriptives exist for the music and sounds in the video stream. This is equivalent to the HLS \"public.accessibility.describes-music-and-sound\" characteristic.
easyReaderarray of StringsA flag indicating whether subtitles are easy to read.
|\n| overrideSystem | boolean | Specify whether to use the channel's preferences over the system preferences (true) or use the channel's preferences only when the system preferences do not match any of the available tracks (false), which allows the channel to provide additional rules in this case. The default value is false. |\n\n\\*\\*Example\\*\\* \\`\\`\\` video.subtitleSelectionPreferences = { values: \\[ { language: \\[\"es-419\", \"es\", \"es-\\*\", \"fr\", \"en-US\", \"en-UK\", \"en\"\\] }, { caption: \"true\" }, { descriptive: \\[\"false\"\\] }, { easyReader: \"true\" } \\], overrideSystem: true } \\`\\`\\` \\*\\*Explanation\\*\\* The subititle language with the highest priority is \"es\" with a country code of \"419\". The next highest priority language is \"es\" with no country code, and then \"es\" with any country code.", - "name": "subtitleSelectionPreferences (_Available since Roku OS 12.5_)", + "name": "subtitleSelectionPreferences", + "type": "roAssociativeArray" + }, + { + "accessPermission": "READ_WRITE", + "default": "", + "description": "The identifier of the selected subtitle track. Subtitles may or may not be visible on the screen, depending upon the user's caption mode setting. Reading this field will return the identifier of the subtitle track selected by the user. Writing this the field will change the track. See also: \\[globalCaptionMode\\](#closed-caption-fields)", + "name": "subtitleTrack", + "type": "string" + }, + { + "accessPermission": "READ_WRITE", + "default": "50", + "description": "Sets the volume of the description tracks separately from the main audio track. The field value can range from 0 to 100.", + "name": "supplementaryAudioVolume", + "type": "int" + }, + { + "accessPermission": "READ_WRITE", + "default": "false", + "description": "Suppresses the closed caption for the purpose of resolving conflicts in cases where UI elements are drawn. Note that most of the disabling/enabling of the captions are done by the Roku OS, including enabling closed caption for Instant Replay.", + "name": "suppressCaptions", + "type": "boolean" + }, + { + "accessPermission": "READ_WRITE", + "default": "[]", + "description": "Contains the information about HLS and DASH standard thumbnail tiles as they are discovered within the manifest for streams which contain them. This field was first introduced (for VOD only) starting in Roku OS 9.1. Starting with Roku OS 11.0, the channel can enable this field for HLS and DASH live streams containing standard thumbnails by setting enableThumbnailTilesDuringLive to true. > For Roku OS releases before 9.4, the \\*\\*thumbnailTiles\\*\\* associative array has the following structure: {tile\\\\\\_id: tile\\\\\\_set} (string to associative array) > > For Roku OS 9.4 and later, the \\*\\*thumbnailTiles\\*\\* associative array has the following structure: {tile\\\\\\_id: \\\\\\[tile\\\\\\_set, tile\\\\\\_set, tile\\\\\\_set,...\\\\\\]}(string to array of associative arrays). This format allows discontinuous tile\\\\\\_sets of the same resolution to be grouped together as a \"choice\" for display. The \\*\\*tile\\\\\\_id\\*\\* field is a unique string identifier for the \\*\\*tile\\\\\\_set\\*\\*, which is an associative array containing the attributes of the tile set as well as information about the thumbnails. The \\*\\*tile\\\\\\_set\\*\\* field contains the following fields:\n\n| Field | Type | Default | Description |\n| --- | --- | --- | --- |\n| htiles | integer | 0 | Horizontal number of thumbnails in a tile (columns.) |\n| vtiles | integer | 0 | Vertical number of thumbnails in a tile (rows.) |\n| width | integer | 0 | Number of horizontal pixels in a thumbnail (this is not the tile as the one in the DASH spec). |\n| height | integer | 0 | Number of vertical pixels in a thumbnail (this is not the same tile as the one in the DASH spec). |\n| bandwidth | integer | 0 | Max tile size in bits / duration. |\n| duration | float | 0.0 | Duration of one tile in seconds (assuming a full tile). |\n| initial\\_time _Available since Roku OS 10.0_ | float | 0.0 | Presentation start time of current **tile\\_set** in seconds. Thumbnails in tiles beginning before this time should be skipped, and the first relevant thumbnail duration should be updated accordingly. |\n| final\\_time | float | 0.0 | End time of current tile\\_set in seconds. |\n| tiles | roArray | \\[\\] | Contains information about each tile in the **tile\\_set**. This contains the following fields: * url (index 0). A string with the URL of the tile. * start\\_time (index 1). A float with the start time of the tile in seconds.
To implement CVAA/screen reader support in your channel, use the [roAudioGuide](https://developer.roku.com/docs/references/brightscript/components/roaudioguide.md component object. The roTextToSpeech component object is typically used for book readers and other special-purpose applications.\n> \n> Please note this component is only available on the following devices: Roku Streaming Stick (3600X), Roku Express (3700X) and Express+ (3710X), Roku Premiere (4620X) and Premiere+ (4630X), Roku Ultra (4640X), and any Roku TV running Roku OS version 7.2 and later.", + "description": "> To implement CVAA/screen reader support in your channel, use the [roAudioGuide](https://developer.roku.com/docs/references/brightscript/components/roaudioguide.md component object. The roTextToSpeech component object is typically used for book readers and other special-purpose applications.", "implementers": [ { "description": "The roTextToSpeech component provides text to speech capabilities to applications", @@ -16242,10 +17665,18 @@ ], "methods": [ { - "description": "Returns the value as a string.", + "description": "**OVERLOADED METHOD**\n\nReturns the value as a string.\n\n or \n\nReturns the object's value formatted as a string according to the specified printf-like [format string](https://developer.roku.com/docs/references/brightscript/language/global-string-functions.mdformat-strings).", "name": "ToStr", - "params": [], - "returnDescription": "The string.", + "params": [ + { + "default": null, + "description": "", + "isRequired": false, + "name": "format", + "type": "String" + } + ], + "returnDescription": "The string.\n\n or \n\nThe formatted string.", "returnType": "String" } ], @@ -16568,14 +17999,14 @@ "default": null, "description": "The minimum transfer rate required to transfer data.", "isRequired": true, - "name": "bytes_per_second", + "name": "bytes\\_per\\_second", "type": "Integer" }, { "default": null, "description": "The interval to be used for averaging bytes\\_per\\_second. For large file transfers and a small bytes\\_per\\_second, averaging over fifteen minutes or even longer might be appropriate. If the transfer is being done over the internet, setting this to a small number because it may cause temporary drops in performance if network problems occur.", "isRequired": true, - "name": "period_in_seconds", + "name": "period\\_in\\_seconds", "type": "Integer" } ], @@ -16881,6 +18312,7 @@ }, { "deprecatedDescription": "This function is deprecated. Roku no longer supports Macrovision and this function exists as a no-op so that legacy scripts do not break.", + "description": "This function is deprecated. Roku no longer supports Macrovision and this function exists as a no-op so that legacy scripts do not break.", "isDeprecated": true, "name": "SetMacrovisionLevel", "params": [ @@ -16950,7 +18382,7 @@ "default": null, "description": "An array of timedMetaData keys for the channel to receive from the timedMetaData event. If the keys array is empty, all the timed metadata associated with the current stream is sent with the isTimedMetaData event. If the keys array is invalid, then do not return any keys to the BrightScript channel. Any keys not specified with this method are deleted by the Roku OS and never returned to the BrightScript application.", "isRequired": true, - "name": "keys", + "name": "keys\\[\\]", "type": "Dynamic" } ], @@ -17070,7 +18502,7 @@ "default": null, "description": "Specifies whether the output begins with a standard XML declaration specifying UTF-8 encoding.", "isRequired": true, - "name": "gen_header", + "name": "gen\\_header", "type": "Boolean" } ], @@ -17165,6 +18597,7 @@ "returnType": "String" }, { + "description": "Checks whether the element has the specified attribute.", "name": "HasAttribute", "params": [ { @@ -17338,66 +18771,79 @@ ], "methods": [ { + "description": "Checks whether a segment in an adaptive stream (HLS, Smooth, or DASH) has been downloaded. This method returns true if a segment in an adaptive stream (HLS, Smooth, or DASH) has been downloaded; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage(), GetIndex() and GetInfo() methods on the event.", "name": "isDownloadSegmentInfo", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether an event has been fired when the format of all tracks in the media stream have been identified.", "name": "isFormatDetected", "params": [], "returnType": "Boolean" }, { + "description": "Audio playback completed at end of content.", "name": "isFullResult", "params": [], "returnType": "Boolean" }, { + "description": "A stream has been selected to start playing.", "name": "isListItemSelected", "params": [], "returnType": "Boolean" }, { + "description": "Audio playback was interrupted", "name": "isPartialResult", "params": [], "returnType": "Boolean" }, { + "description": "Audio playback was paused by the user.", "name": "isPaused", "params": [], "returnType": "Boolean" }, { + "description": "Audio playback failed due to an error", "name": "isRequestFailed", "params": [], "returnType": "Boolean" }, { + "description": "Stream playback has completed successfully.", "name": "isRequestSucceeded", "params": [], "returnType": "Boolean" }, { + "description": "Audio playback has resumed", "name": "isResumed", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the individual segments in an HLS or smooth stream are about to be downloaded. This method returns true if segments in the stream are going to be downloaded; otherwise, it returns false.", "name": "isSegmentDownloadStarted", "params": [], "returnType": "Boolean" }, { + "description": "Status information is available.", "name": "isStatusMessage", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether playback has begun of a segment in an HLS, DASH, or smooth stream. This method returns true if the playback of a segment in an HLS, DASH, or smooth stream has begun; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage(), GetIndex() and GetInfo() methods on the event.", "name": "isStreamSegmentInfo", "params": [], "returnType": "Boolean" }, { + "description": "This event is fired when an ID3 timecode has passed with an event that includes key/value pairs for timed metadata that the Brightscript channel is interested in.", "name": "isTimedMetaData", "params": [], "returnType": "Boolean" @@ -17417,16 +18863,19 @@ ], "methods": [ { + "description": "The index value of this event is not used and is always set to 0.", "name": "GetIndex", "params": [], "returnType": "Integer" }, { + "description": "Returns an associative array with the following key/value pairs:", "name": "GetInfo", "params": [], "returnType": "Object" }, { + "description": "Returns the string \"CECStatus\".", "name": "GetMessage", "params": [], "returnType": "String" @@ -17446,16 +18895,19 @@ ], "methods": [ { + "description": "Checks whether the previous GET request has completed failed.", "name": "isRequestFailed", "params": [], "returnType": "Boolean" }, { + "description": "Checks if the previous GET request did not complete. This method returns true if the request was not complete; otherwise, it returns false.", "name": "isRequestInterrupted", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the previous GET request has completed successfully.", "name": "isRequestSucceeded", "params": [], "returnType": "Boolean" @@ -17475,11 +18927,13 @@ ], "methods": [ { + "description": "Indicates whether the user has changed the closed caption mode or track. This method returns true if the caption mode changed; otherwise, it returns false.", "name": "isCaptionModeChanged", "params": [], "returnType": "Boolean" }, { + "description": "Checks if the device status has changed. This method returns true if the device status has changed; otherwise, it returns false.", "name": "isStatusMessage", "params": [], "returnType": "Boolean" @@ -17499,11 +18953,13 @@ ], "methods": [ { + "description": "Checks if a storage device was inserted in the USB port. This method returns true if a storage device was inserted; otherwise, it returns false.", "name": "isStorageDeviceAdded", "params": [], "returnType": "Boolean" }, { + "description": "Checks if a storage device was removed from the USB port. This method returns true if a storage device was removed; otherwise, it returns false.", "name": "isStorageDeviceRemoved", "params": [], "returnType": "Boolean" @@ -17518,6 +18974,7 @@ "implementers": [], "methods": [ { + "description": "Checks if an HDMI status event has occurred. This method returns true if an HDMI status event has occurred; otherwise, it returns false.", "name": "isHdmiStatus", "params": [], "returnType": "Boolean" @@ -17537,11 +18994,13 @@ ], "methods": [ { + "description": "Returns an roAssociativeArray describing the input event, which may be one of the following values:", "name": "GetInfo", "params": [], "returnType": "Object" }, { + "description": "Checks if an input event was received. This method returns true if an input event was received; otherwise, it returns false.", "name": "isInput", "params": [], "returnType": "Boolean" @@ -17561,11 +19020,13 @@ ], "methods": [ { + "description": "Checks if the microphone recording session has been closed. This method returns true if the recording session is closed; otherwise, it returns false.", "name": "IsRecordingDone", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the microphone is open. This method returns true when the microphone is open; otherwise, it returns false.", "name": "IsRecordingInfo", "params": [], "returnType": "Boolean" @@ -17585,26 +19046,31 @@ ], "methods": [ { + "description": "Retrieves the new field value at the time of the change.", "name": "getData", "params": [], "returnType": "Dynamic" }, { + "description": "Retrieves the name of the field that changed.", "name": "getField", "params": [], "returnType": "Dynamic" }, { + "description": "Retrieves an AA that contains the values of selected \"context\" fields, which were [specified in an earlier-executed call](https://developer.roku.com/docs/references/brightscript/interfaces/ifsgnodefield.mdevent-field-aggregation) to `observeField()` or `observeFieldScoped()`. (If no such \"context\" fields were designated previously, `getInfo()` returns an empty AA.) The array is keyed on the names of the \"context\" fields, and the entry values are the instantaneous values of the corresponding fields, at the point when the observed field changed.", "name": "getInfo", "params": [], "returnType": "Object" }, { + "description": "Retrieves the ID of the node that changed.", "name": "getNode", "params": [], "returnType": "Dynamic" }, { + "description": "Retrieves a pointer to the node. This can be used for nodes without an ID.", "name": "getRoSGNode", "params": [], "returnType": "Dynamic" @@ -17624,6 +19090,7 @@ ], "methods": [ { + "description": "Checks whether the screen has been closed and is no longer displayed to the user. This method returns true if the screen was closed; otherwise, it returns false.", "name": "isScreenClosed", "params": [], "returnType": "Boolean" @@ -17647,6 +19114,7 @@ ], "methods": [ { + "description": "Returns the ID of the socket this event is for. The ID of a socket can be obtained from ifSocketAsync.GetID(). Use [ifSocketStatus](https://developer.roku.com/docs/references/brightscript/interfaces/ifsocketstatus.md\"ifSocketStatus\") or [ifSocketConnectionStatus](https://developer.roku.com/docs/references/brightscript/interfaces/ifsocketconnectionstatus.md\"ifSocketConnectionStatus\") on the indicated socket to query the new status for the socket.", "name": "GetSocketID", "params": [], "returnType": "Integer" @@ -17661,6 +19129,7 @@ "implementers": [], "methods": [ { + "description": "Returns an AssociativeArray containing information describing the event, which may be one of the following values:", "name": "GetInfo", "params": [], "returnType": "Object" @@ -17680,16 +19149,19 @@ ], "methods": [ { + "description": "Returns the description of the speech, which may be one of the following values:", "name": "GetData", "params": [], "returnType": "Integer" }, { + "description": "The text to speech service may be shared among any number of clients; therefore, the IDs returned to a given client are not necessarily contiguous. The value is only meaningful if [GetData()](https://developer.roku.com/docs/references/brightscript/events/rotexttospeechevent.mdgetinfo-as-object \"GetData()\") returned 0, 1 or 2; it returns 0 otherwise.", "name": "GetIndex", "params": [], "returnType": "Integer" }, { + "description": "Returns an associative array with the following key-value pairs, depending the value returned by the [GetData()](https://developer.roku.com/docs/references/brightscript/events/rotexttospeechevent.mdgetinfo-as-object \"GetData()\") method:", "name": "GetInfo", "params": [], "returnType": "Object" @@ -17709,21 +19181,25 @@ ], "methods": [ { + "description": "Returns an roBitmap from the request if the state is ready.", "name": "GetBitmap", "params": [], "returnType": "Object" }, { + "description": "Returns the unique id of the request.", "name": "GetId", "params": [], "returnType": "Integer" }, { + "description": "Returns the state of the request. See [ifTextureRequest](https://developer.roku.com/docs/references/brightscript/interfaces/iftexturerequest.md\"ifTextureRequest\").GetState() for the list of states.", "name": "GetState", "params": [], "returnType": "Integer" }, { + "description": "Returns the URI of the request.", "name": "GetURI", "params": [], "returnType": "String" @@ -17743,31 +19219,37 @@ ], "methods": [ { + "description": "Returns the Unicode character value generated by a keyboard key press event.", "name": "GetChar", "params": [], "returnType": "Integer" }, { + "description": "Returns the ID of the remote button or character value represented by the event. Values 0x20..0x10FFFF represent a Unicode character value. Other values represent a remote button ID. GetID() returns the same value for a release event as for the press event.", "name": "GetID", "params": [], "returnType": "Integer" }, { + "description": "Returns an integer representing pressed or released keys on the remote. This table can be accessed at runtime by calling `bslCore.brs: bslUniversalControlEventCodes()`:", "name": "GetInt", "params": [], "returnType": "Integer" }, { + "description": "Returns the ID of the remote button that generated an event.", "name": "GetKey", "params": [], "returnType": "Integer" }, { + "description": "Returns an identifier associated with the type and ID of the remote control that generated the event. For example, if multiple wireless remotes are paired, this may be used to distinguish the event sources. Some remote controls, such as IR remotes, can not be distinguished using this API.", "name": "GetRemoteID", "params": [], "returnType": "String" }, { + "description": "Checks whether a press event has occurred. This method returns true if a press event has occurred; otherwise, it returns false.", "name": "IsPress", "params": [], "returnType": "Boolean" @@ -17787,41 +19269,49 @@ ], "methods": [ { + "description": "Returns a description of the failure that occurred.", "name": "GetFailureReason", "params": [], "returnType": "String" }, { + "description": "Returns the type of event, which may be one of the following values:", "name": "GetInt", "params": [], "returnType": "Integer" }, { + "description": "Returns the protocol response code associated with this event. For a successful HTTP request this will be the HTTP status code 200. For unexpected errors the return value is negative. There are lots of possible negative errors from the CURL library but it's often best just to look at the text version via GetFailureReason().", "name": "GetResponseCode", "params": [], "returnType": "Integer" }, { + "description": "Returns an [roAssociativeArray](https://developer.roku.com/docs/references/brightscript/components/roassociativearray.md\"roAssociativeArray\") containing all the headers returned by the server for appropriate protocols (such as HTTP). Headers are only returned when the status code is greater than or equal to 200 and less than 300", "name": "GetResponseHeaders", "params": [], "returnType": "Object" }, { + "description": "This method returns an [roArray](https://developer.roku.com/docs/references/brightscript/components/roarray.md\"roArray\") of [roAssociativeArrays](https://developer.roku.com/docs/references/brightscript/components/roassociativearray.md\"roAssociativeArray\"), where each associative array contains a single header name/value pair. Use this function if you need access to duplicate headers, since GetResponseHeaders() returns only the last name/value pair for a given name. All headers are returned regardless of the status code", "name": "GetResponseHeadersArray", "params": [], "returnType": "Object" }, { + "description": "Returns a magic number that can be matched with the value returned by the [roUrlTransfer.GetIdentity()](https://developer.roku.com/docs/references/brightscript/interfaces/ifurltransfer.mdgetidentity-as-integer) method to determine the source of the roUrlTransfer event.", "name": "GetSourceIdentity", "params": [], "returnType": "Integer" }, { + "description": "For transfer complete AsyncGetToString, AsyncPostFromString and AsnycPostFromFile requests this will be the actual response body from the server. This method returns the string associated with the event.", "name": "GetString", "params": [], "returnType": "String" }, { + "description": "Returns the IP address of the destination.", "name": "GetTargetIpAddress", "params": [], "returnType": "String" @@ -17841,86 +19331,103 @@ ], "methods": [ { + "description": "Returns current position in the stream (in seconds) from the beginning.", "name": "GetIndex", "params": [], "returnType": "Integer" }, { + "description": "Returns an roAssociativeArray array with the following key-value pairs:", "name": "GetInfo", "params": [], "returnType": "Object" }, { + "description": "Checks whether closed caption mode or track has been changed by the user. This method returns true if closed caption mode or track has been changed by the user; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage(), GetIndex(), and GetInfo() methods on the event.", "name": "isCaptionModeChanged", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether a segment in an adaptive stream (HLS, Smooth, or DASH) has been downloaded. This method returns true if a segment in an adaptive stream (HLS, Smooth, or DASH) has been downloaded; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage(), GetIndex() and GetInfo() methods on the event.", "name": "isDownloadSegmentInfo", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether an event has been fired when the format of all tracks in the media stream have been identified.", "name": "isFormatDetected", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether video playback has completed at the end of the content list. This method returns true if video playback has completed at the end of the content list; otherwise, it returns false.", "name": "isFullResult", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the video player is about to start playing a new item in the content list. This method returns true if a new item in the content list was selected; otherwise, it returns false.", "name": "isListItemSelected", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether video playback was paused by the user. This method returns true if video playback was paused; otherwise, it returns false.", "name": "isPaused", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the current position in the video stream has changed. This event is sent periodically while playing, as determined by the last call to [ifVideoPlayer.SetPositionNotificationPeriod](https://developer.roku.com/docs/references/brightscript/interfaces/ifvideoplayer.mdsetpositionnotificationperiodperiod-as-integer-as-void \"ifVideoPlayer.SetPositionNotificationPeriod\"). This method returns true if the current position in the video stream has changed; otherwise, it returns false. Specific information about the event can be obtained by calling the GetIndex() and GetInfo() methods on the event.", "name": "isPlaybackPosition", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether video playback has failed. This method returns true if video playback failed; otherwise, it returns false.", "name": "isRequestFailed", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the player has finished playing an item in the content list. This method returns true if the player has finished playing a content list item; otherwise, it returns false. Specific information about the event can be obtained by calling the GetIndex() method on the event.", "name": "isRequestSucceeded", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether video playback has resumed. This method returns true if video playback has resumed; otherwise, it returns false.", "name": "isResumed", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the individual segments in an HLS or smooth stream are about to be downloaded. This method returns true if segments in the stream are going to be downloaded; otherwise, it returns false.", "name": "isSegmentDownloadStarted", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether status information or other diagnostic message is available. This method returns true if status information or diagnostic message is available; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage() method on the event.", "name": "isStatusMessage", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether playback has begun of a segment in an HLS, DASH, or smooth stream. This method returns true if the playback of a segment in an HLS, DASH, or smooth stream has begun; otherwise, it returns false. Specific information about the event can be obtained by calling the GetMessage(), GetIndex() and GetInfo() methods on the event.", "name": "isStreamSegmentInfo", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether the video stream has started playing. This method returns true if the video stream has started playing; otherwise, it returns false. Specific information about the event can be obtained by calling the GetIndex() and GetInfo() methods on the event.", "name": "isStreamStarted", "params": [], "returnType": "Boolean" }, { + "description": "Checks whether an ID3 timecode has passed with an event that includes key-value pairs for timed metadata that the BrightScript channel is interested in.", "name": "isTimedMetaData", "params": [], "returnType": "Boolean" diff --git a/src/types/BuiltInInterfaceAdder.ts b/src/types/BuiltInInterfaceAdder.ts index b6aeaa98f..30b1b8484 100644 --- a/src/types/BuiltInInterfaceAdder.ts +++ b/src/types/BuiltInInterfaceAdder.ts @@ -61,6 +61,10 @@ export class BuiltInInterfaceAdder { } for (const method of ifaceData.methods ?? []) { + if (ifaceData.name.toLowerCase() === 'ifintops' && method.name.toLowerCase() === 'tostr') { + // handle special case - this messed up the .toStr() method on integers + continue; + } const methodFuncType = this.buildMethodFromDocData(method, overrides, thisType); builtInMemberTable.addSymbol(method.name, { description: method.description, completionPriority: 1 }, methodFuncType, SymbolTypeFlag.runtime); } diff --git a/src/util.ts b/src/util.ts index bdfbefc4b..b95b62e41 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1255,7 +1255,20 @@ export class Util { * @returns {BscType} the known type, or dynamic */ public getNodeFieldType(typeDescriptor: string, lookupTable?: SymbolTable): BscType { - const typeDescriptorLower = typeDescriptor.toLowerCase().trim(); + let typeDescriptorLower = typeDescriptor.toLowerCase().trim().replace(/\*/g, ''); + + if (typeDescriptorLower.startsWith('as ')) { + typeDescriptorLower = typeDescriptorLower.substring(3).trim(); + } + const nodeFilter = (new RegExp(/^\[?(.* node)/, 'i')).exec(typeDescriptorLower); + if (nodeFilter?.[1]) { + typeDescriptorLower = nodeFilter[1].trim(); + } + const parensFilter = (new RegExp(/(.*)\(.*\)/, 'gi')).exec(typeDescriptorLower); + if (parensFilter?.[1]) { + typeDescriptorLower = parensFilter[1].trim(); + } + const bscType = this.tokenToBscType(createToken(TokenKind.Identifier, typeDescriptorLower)); if (bscType) { return bscType; @@ -1274,9 +1287,19 @@ export class Util { return unionTypeFactory([IntegerType.instance, StringType.instance]); } + //check for uniontypes + const multipleTypes = typeDescriptorLower.split(' or ').map(s => s.trim()); + if (multipleTypes.length > 1) { + const individualTypes = multipleTypes.map(t => this.getNodeFieldType(t, lookupTable)); + return unionTypeFactory(individualTypes); + } + + const typeIsArray = typeDescriptorLower.startsWith('array of ') || typeDescriptorLower.startsWith('roarray of '); - if (typeDescriptorLower.startsWith('array of ')) { - let arrayOfTypeName = typeDescriptorLower.substring(9); //cut off beginning 'array of' + if (typeIsArray) { + const ofSearch = ' of '; + const arrayPrefixLength = typeDescriptorLower.indexOf(ofSearch) + ofSearch.length; + let arrayOfTypeName = typeDescriptorLower.substring(arrayPrefixLength); //cut off beginnin, eg. 'array of' or 'roarray of' if (arrayOfTypeName.endsWith('s')) { // remove "s" in "floats", etc. arrayOfTypeName = arrayOfTypeName.substring(0, arrayOfTypeName.length - 1); @@ -1293,6 +1316,8 @@ export class Util { } else if (typeDescriptorLower.startsWith('value ')) { const actualTypeName = typeDescriptorLower.substring('value '.length); //cut off beginning 'value ' return this.getNodeFieldType(actualTypeName, lookupTable); + } else if (typeDescriptorLower === 'n/a') { + return DynamicType.instance; } else if (typeDescriptorLower === 'uri') { return StringType.instance; } else if (typeDescriptorLower === 'color') { @@ -1317,9 +1342,12 @@ export class Util { return StringType.instance; } else if (typeDescriptorLower === 'bool') { return BooleanType.instance; - } else if (typeDescriptorLower === 'array') { + } else if (typeDescriptorLower === 'array' || typeDescriptorLower === 'roarray') { return new ArrayType(); - } else if (typeDescriptorLower === 'assocarray' || typeDescriptorLower === 'associative array' || typeDescriptorLower === 'associativearray') { + } else if (typeDescriptorLower === 'assocarray' || + typeDescriptorLower === 'associative array' || + typeDescriptorLower === 'associativearray' || + typeDescriptorLower === 'roassociativearray') { return new AssociativeArrayType(); } else if (typeDescriptorLower === 'node') { return ComponentType.instance; @@ -1329,12 +1357,21 @@ export class Util { return getRect2dType(); } else if (typeDescriptorLower === 'rect2darray') { return new ArrayType(getRect2dType()); + } else if (typeDescriptorLower === 'font') { + return this.getNodeFieldType('roSGNodeFont', lookupTable); + } else if (typeDescriptorLower === 'contentnode') { + return this.getNodeFieldType('roSGNodeContentNode', lookupTable); + } else if (typeDescriptorLower.endsWith(' node')) { + return this.getNodeFieldType('roSgNode' + typeDescriptorLower.substring(0, typeDescriptorLower.length - 5), lookupTable); } else if (lookupTable) { //try doing a lookup - return lookupTable.getSymbolType(typeDescriptorLower, { flags: SymbolTypeFlag.typetime }); + return lookupTable.getSymbolType(typeDescriptorLower, { + flags: SymbolTypeFlag.typetime, + fullName: typeDescriptor, + tableProvider: () => lookupTable + }); } - // TODO: Handle 'rect2d', 'rect2dArray', return DynamicType.instance; } From 0f4e108f289c0c7e9f8b33a7bf30fb41a8a2600a Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Wed, 8 May 2024 17:07:19 -0300 Subject: [PATCH 165/460] Fixes all overloaded methods in interfaces from Scraped Roku docs (#1163) --- scripts/scrape-roku-docs.ts | 31 ++++--- src/Program.spec.ts | 19 +++++ src/roku-types/data.json | 161 ++++++++---------------------------- 3 files changed, 72 insertions(+), 139 deletions(-) diff --git a/scripts/scrape-roku-docs.ts b/scripts/scrape-roku-docs.ts index 4210520b1..513b5819c 100644 --- a/scripts/scrape-roku-docs.ts +++ b/scripts/scrape-roku-docs.ts @@ -1186,21 +1186,25 @@ class Runner { interfaces: {} }); - // fix ifStringOp overloads - fixOverloadedMethod(this.result.interfaces.ifstringops, 'instr'); - fixOverloadedMethod(this.result.interfaces.ifstringops, 'mid'); - fixOverloadedMethod(this.result.interfaces.ifstringops, 'startsWith'); - fixOverloadedMethod(this.result.interfaces.ifstringops, 'endswith'); - - // fix ifSGNodeField overloads - fixOverloadedMethod(this.result.interfaces.ifsgnodefield, 'observeField'); - fixOverloadedMethod(this.result.interfaces.ifsgnodefield, 'observeFieldScoped'); + // fix all overloaded methods in interfaces + for (const ifaceKey in this.result.interfaces) { + const iface = this.result.interfaces[ifaceKey]; + const overloadedMethods = new Set(); + const methodDefs = new Set(); + for (const method of iface.methods) { + const lowerMethodName = method.name.toLowerCase(); + if (methodDefs.has(lowerMethodName)) { + overloadedMethods.add(lowerMethodName); + } else { + methodDefs.add(lowerMethodName); + } + } - // fix ifdraw2d overloads - fixOverloadedMethod(this.result.interfaces.ifdraw2d, 'drawScaledObject'); + for (const methodName of overloadedMethods) { + fixOverloadedMethod(iface, methodName); + } - // fix ifToStr overloads - fixOverloadedMethod(this.result.interfaces.iftostr, 'toStr'); + } //fix roSGNodeContentNode overloads fixOverloadedField(this.result.nodes.contentnode, 'actors'); @@ -1288,6 +1292,7 @@ function fixOverloadedMethod(iface: RokuInterface, funcName: string) { iface.methods = iface.methods.filter(method => method.name.toLowerCase() !== funcName.toLowerCase()); // add to list iface.methods.push(mergedFunc); + console.log('Fixed overloaded method', `${iface.name}.${funcName}`); } function fixOverloadedField(node: SceneGraphNode, fieldName: string) { diff --git a/src/Program.spec.ts b/src/Program.spec.ts index a78f3bb20..ccb5c28db 100644 --- a/src/Program.spec.ts +++ b/src/Program.spec.ts @@ -2863,6 +2863,25 @@ describe('Program', () => { expectTypeToBe(trickBarType.getMemberType('trackImageUri', opts), StringType); expectTypeToBe(trickBarType.getMemberType('trackBlendColor', opts), UnionType); }); + + it('deals with roDateTime overloaded method', () => { + program.setFile('source/main.bs', ` + namespace alpha + function getDate() as roDateTime + theDate = createObject("roDateTime") + return theDate + end function + end namespace + + function toUtcTime(localTime as roDateTime) as roDateTime + theDate = alpha.getDate() + return theDate + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); + }); describe('manifest', () => { diff --git a/src/roku-types/data.json b/src/roku-types/data.json index 26f1d7e11..ffdf05f3e 100644 --- a/src/roku-types/data.json +++ b/src/roku-types/data.json @@ -1,5 +1,5 @@ { - "generatedDate": "2024-05-08T16:30:56.090Z", + "generatedDate": "2024-05-08T18:23:20.097Z", "nodes": { "animation": { "description": "Extends [**AnimationBase**](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/animationbase.md\n\nThe Animation node class provides animations of renderable nodes, by applying interpolator functions to the values in specified renderable node fields. For an animation to take effect, an Animation node definition must include a child field interpolator node ([FloatFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/floatfieldinterpolator.md\"FloatFieldInterpolator\"), [Vector2DFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/vector2dfieldinterpolator.md\"Vector2DFieldInterpolator\"), [ColorFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/colorfieldinterpolator.md\"ColorFieldInterpolator\")) definition for each renderable node field that is animated.\n\nThe Animation node class provides a simple linear interpolator function, where the animation takes place smoothly and simply from beginning to end. The Animation node class also provides several more complex interpolator functions to allow custom animation effects. For example, you can move a graphic image around the screen at differing speeds and curved trajectories at different times in the animation by specifying the appropriate function in the easeFunction field (quadratic and exponential are two examples of functions that can be specified). The interpolator functions are divided into two parts: the beginning of the animation (ease-in), and the end of the animation (ease-out). You can apply a specified interpolator function to either or both ease-in and ease-out, or specify no function for either or both (which is the linear function). You can also change the portion of the animation that is ease-in and ease-out to arbitrary fractional values for a quadratic interpolator function applied to both ease-in and ease-out.", @@ -9881,26 +9881,12 @@ "returnType": "Void" }, { - "description": "This method triggers the start of the audio resource sound playback. The effect of Trigger(volume) is identical to Trigger(volume, 0).", + "description": "**OVERLOADED METHOD**\n\nThis method triggers the start of the audio resource sound playback. The effect of Trigger(volume) is identical to Trigger(volume, 0).\n\n or \n\nTriggers the start of the audio resource sound playback. This method will interrupt any playing sound when the index is the same. It will mix with any playing sound if the index is different.", "name": "Trigger", "params": [ { "default": null, - "description": "The volume is a number between 0 and 100 (percentage of full volume). A value of 50 should be used for normal volume.", - "isRequired": true, - "name": "volume", - "type": "Integer" - } - ], - "returnType": "Void" - }, - { - "description": "Triggers the start of the audio resource sound playback. This method will interrupt any playing sound when the index is the same. It will mix with any playing sound if the index is different.", - "name": "Trigger", - "params": [ - { - "default": null, - "description": "The volume is a number between 0 and 100 (percentage of full volume). 50 should be used for normal volume.", + "description": "The volume is a number between 0 and 100 (percentage of full volume). A value of 50 should be used for normal volume. OR The volume is a number between 0 and 100 (percentage of full volume). 50 should be used for normal volume.", "isRequired": true, "name": "volume", "type": "Integer" @@ -9908,11 +9894,12 @@ { "default": null, "description": "The index is a value between 0 and [MaxSimulStreams()](#maxsimulstreams-as-integer) allowing for multiple sounds to be mixed.", - "isRequired": true, + "isRequired": false, "name": "index", "type": "Integer" } ], + "returnDescription": "", "returnType": "Void" } ], @@ -9966,27 +9953,12 @@ ], "methods": [ { - "description": "Appends the contents of the Byte Array to the specified file.", + "description": "**OVERLOADED METHOD**\n\nAppends the contents of the Byte Array to the specified file.", "name": "AppendFile", "params": [ { "default": null, - "description": "The path to the file to be appended to the ByteArray.", - "isRequired": true, - "name": "path", - "type": "String" - } - ], - "returnDescription": "A flag indicating whether the file was successfully appended to the calling ByteArray.", - "returnType": "Boolean" - }, - { - "description": "Appends the contents of the Byte Array to the specified file.", - "name": "AppendFile", - "params": [ - { - "default": null, - "description": "The path to the file to be appended to the Byte Array.", + "description": "The path to the file to be appended to the ByteArray. OR The path to the file to be appended to the Byte Array.", "isRequired": true, "name": "path", "type": "String" @@ -9994,14 +9966,14 @@ { "default": null, "description": "The position in the file from which to start appending bytes.", - "isRequired": true, + "isRequired": false, "name": "start\\_pos", "type": "Integer" }, { "default": null, "description": "The length of the bytes to be appended to the Byte Array, starting from the specified starting position.", - "isRequired": true, + "isRequired": false, "name": "length", "type": "Integer" } @@ -10052,27 +10024,20 @@ "returnType": "Void" }, { - "description": "Calculates a CRC-32 of the contents of the Byte Array.", - "name": "GetCRC32", - "params": [], - "returnDescription": "The calculated CRC-32 checksum.", - "returnType": "Integer" - }, - { - "description": "Calculates a CRC-32 of a subset of bytes within the Byte Array.", + "description": "**OVERLOADED METHOD**\n\nCalculates a CRC-32 of the contents of the Byte Array.\n\n or \n\nCalculates a CRC-32 of a subset of bytes within the Byte Array.", "name": "GetCRC32", "params": [ { "default": null, "description": "The starting index of the subset of bytes to be used in the CRC-32 calculation.", - "isRequired": true, + "isRequired": false, "name": "start", "type": "Integer" }, { "default": null, "description": "The length of the bytes to be included.", - "isRequired": true, + "isRequired": false, "name": "length", "type": "Integer" } @@ -10118,22 +10083,7 @@ "returnType": "Boolean" }, { - "description": "Reads the specified file into the Byte Array. Any data currently in the Byte Array is discarded.", - "name": "ReadFile", - "params": [ - { - "default": null, - "description": "The path to the file to be read.", - "isRequired": true, - "name": "path", - "type": "String" - } - ], - "returnDescription": "A flag indicating whether the bytes were successfully read into the Byte Array.", - "returnType": "Boolean" - }, - { - "description": "Reads the specified file into the Byte Array. Any data currently in the Byte Array is discarded.", + "description": "**OVERLOADED METHOD**\n\nReads the specified file into the Byte Array. Any data currently in the Byte Array is discarded.", "name": "ReadFile", "params": [ { @@ -10146,14 +10096,14 @@ { "default": null, "description": "The index of the file from which to start reading bytes.", - "isRequired": true, + "isRequired": false, "name": "start\\_pos", "type": "Integer" }, { "default": null, "description": "The length of the bytes to be read from the file, starting from the specified starting position.", - "isRequired": true, + "isRequired": false, "name": "length", "type": "Integer" } @@ -10204,22 +10154,7 @@ "returnType": "String" }, { - "description": "Writes the bytes contained in the Byte Array to the specified file.", - "name": "WriteFile", - "params": [ - { - "default": null, - "description": "The path to the file to which the bytes are to be written.", - "isRequired": true, - "name": "path", - "type": "String" - } - ], - "returnDescription": "A flag indicating whether the bytes were successfully written to the file.", - "returnType": "Boolean" - }, - { - "description": "Writes a subset of the bytes contained in the Byte Array to the specified file.", + "description": "**OVERLOADED METHOD**\n\nWrites the bytes contained in the Byte Array to the specified file.\n\n or \n\nWrites a subset of the bytes contained in the Byte Array to the specified file.", "name": "WriteFile", "params": [ { @@ -10232,14 +10167,14 @@ { "default": null, "description": "The index of the calling ByteArray from which to start writing bytes.", - "isRequired": true, + "isRequired": false, "name": "start\\_index", "type": "Integer" }, { "default": null, "description": "The length of the bytes to be written to the file, starting from the specified index.", - "isRequired": true, + "isRequired": false, "name": "length", "type": "Integer" } @@ -10839,24 +10774,18 @@ "returnType": "Void" }, { - "description": "Returns an ISO 8601 representation of the date/time value.", - "name": "ToISOString", - "params": [], - "returnDescription": "ISO 8601 as String, e.g. \"2021-03-25T18:53:03+0000\"", - "returnType": "String" - }, - { - "description": "Returns an ISO 8601 representation of the date/time value with milliseconds precision.", + "description": "**OVERLOADED METHOD**\n\nReturns an ISO 8601 representation of the date/time value.\n\n or \n\nReturns an ISO 8601 representation of the date/time value with milliseconds precision.", "name": "ToISOString", "params": [ { "default": null, - "isRequired": true, + "description": "", + "isRequired": false, "name": "format", "type": "String" } ], - "returnDescription": "ISO 8601 as String with milliseconds precision, e.g. \"2021-03-25T18:53:03.220+0000\"", + "returnDescription": "ISO 8601 as String, e.g. \"2021-03-25T18:53:03+0000\"\n\n or \n\nISO 8601 as String with milliseconds precision, e.g. \"2021-03-25T18:53:03.220+0000\"", "returnType": "String" }, { @@ -12674,39 +12603,32 @@ "returnType": "String" }, { - "description": "Returns the system font at its default size. Calling this method is the same as calling the [GetDefaultFont()](#getdefaultfontsize-as-integer-bold-as-boolean-italic-as-boolean-as-object) method with the following syntax: `reg.GetDefaultFont(reg.GetDefaultFontSize(), false, false)`.", - "name": "GetDefaultFont", - "params": [], - "returnDescription": "The system font as its default size.", - "returnType": "Object" - }, - { - "description": "Returns the system font. The system font is always available, even if the [Register()](#registerpath-as-string-as-boolean) method has not been called", + "description": "**OVERLOADED METHOD**\n\nReturns the system font at its default size. Calling this method is the same as calling the [GetDefaultFont()](#getdefaultfontsize-as-integer-bold-as-boolean-italic-as-boolean-as-object) method with the following syntax: `reg.GetDefaultFont(reg.GetDefaultFontSize(), false, false)`.\n\n or \n\nReturns the system font. The system font is always available, even if the [Register()](#registerpath-as-string-as-boolean) method has not been called", "name": "GetDefaultFont", "params": [ { "default": null, "description": "The requested font size, in pixels, not points.", - "isRequired": true, + "isRequired": false, "name": "size", "type": "Integer" }, { "default": null, "description": "\"bold\" specifies a font variant that may be (but is not always) supported by the font file.", - "isRequired": true, + "isRequired": false, "name": "bold", "type": "Boolean" }, { "default": null, "description": "\"italic\" specifies a font variant that may be (but is not always) supported by the font file.", - "isRequired": true, + "isRequired": false, "name": "italic", "type": "Boolean" } ], - "returnDescription": "An roFont object representing the system font.", + "returnDescription": "The system font as its default size.\n\n or \n\nAn roFont object representing the system font.", "returnType": "Object" }, { @@ -18234,52 +18156,39 @@ "returnType": "Void" }, { - "description": "Sets the target display window for the video.", - "name": "SetDestinationRect", - "params": [ - { - "default": null, - "description": "The parameters of the target display window, which include the x and y coordinates, width, and height {x:Integer, y:Integer, w:Integer, h:Integer} The default value is: {x:0, y:0, w:0, h:0}, which is full screen.", - "isRequired": true, - "name": "rect", - "type": "Object" - } - ], - "returnType": "Void" - }, - { - "description": "Sets the target display window for the video. This is similar to the [SetDestinationRect()](#setdestinationrectrect-as-object-as-void) function except that the values are specified as separate parameters.", + "description": "**OVERLOADED METHOD**\n\nSets the target display window for the video.\n\n or \n\nSets the target display window for the video. This is similar to the [SetDestinationRect()](#setdestinationrectrect-as-object-as-void) function except that the values are specified as separate parameters.", "name": "SetDestinationRect", "params": [ { "default": null, - "description": "The x coordinate of the target display window.", + "description": "The parameters of the target display window, which include the x and y coordinates, width, and height {x:Integer, y:Integer, w:Integer, h:Integer} The default value is: {x:0, y:0, w:0, h:0}, which is full screen. OR The x coordinate of the target display window.", "isRequired": true, - "name": "x", - "type": "Integer" + "name": "rectOrX", + "type": "Object or Integer" }, { "default": null, "description": "The y coordinate of the target display window.", - "isRequired": true, + "isRequired": false, "name": "y", "type": "Integer" }, { "default": null, "description": "The width of the target display window.", - "isRequired": true, + "isRequired": false, "name": "w", "type": "Integer" }, { "default": null, "description": "The height coordinate of the target display window.", - "isRequired": true, + "isRequired": false, "name": "h", "type": "Integer" } ], + "returnDescription": "", "returnType": "Void" }, { From 71de8bdf67ceba09679b9d3515a26fc682347609 Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Thu, 9 May 2024 10:43:56 -0300 Subject: [PATCH 166/460] Hovers on LHS of assignments do symbol type lookups (#1165) --- src/Scope.ts | 3 - src/bscPlugin/hover/HoverProcessor.spec.ts | 96 +++++++++++++++++++++- src/bscPlugin/hover/HoverProcessor.ts | 9 +- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/src/Scope.ts b/src/Scope.ts index 360f8e60a..9bef47668 100644 --- a/src/Scope.ts +++ b/src/Scope.ts @@ -898,9 +898,6 @@ export class Scope { this.linkSymbolTableDisposables.push( ...this._allNamespaceTypeTable.mergeNamespaceSymbolTables(namespaceTypes) ); - /* this.linkSymbolTableDisposables.push( - () => file.unlinkNamespaceSymbolTables() - );*/ } } for (const [_, nsContainer] of this.namespaceLookup) { diff --git a/src/bscPlugin/hover/HoverProcessor.spec.ts b/src/bscPlugin/hover/HoverProcessor.spec.ts index 4e38bbd5a..0626f2c81 100644 --- a/src/bscPlugin/hover/HoverProcessor.spec.ts +++ b/src/bscPlugin/hover/HoverProcessor.spec.ts @@ -45,6 +45,7 @@ describe('HoverProcessor', () => { end function end sub `); + program.validate(); //hover over the `name = 1` line let hover = program.getHover(file.srcPath, util.createPosition(2, 24))[0]; @@ -422,7 +423,10 @@ describe('HoverProcessor', () => { let commentSep = `\n***\n`; //th|ing = new MyKlass() let hover = program.getHover('source/main.bs', util.createPosition(2, 24))[0]; - expect(hover?.contents).to.eql([`${fence('thing as MyKlass')}${commentSep}A sample class`]); + expect(hover?.contents).to.eql([`${fence('thing as MyKlass')}`]); + //thing = new MyK|lass() + hover = program.getHover('source/main.bs', util.createPosition(2, 37))[0]; + expect(hover?.contents).to.eql([`${fence('class MyKlass')}${commentSep}A sample class`]); //use|Klass(thing) hover = program.getHover('source/main.bs', util.createPosition(3, 24))[0]; expect(hover?.contents).to.eql([`${fence('sub useKlass(thing as MyKlass) as void')}${commentSep}Prints a MyKlass.name`]); @@ -676,4 +680,94 @@ describe('HoverProcessor', () => { expect(hover?.contents).eql([fence('function roSGNodeWidget@.someFunc(input as string) as float')]); }); }); + + describe('multiple definition locations', () => { + + it('shows correct type in all locations', () => { + const file = program.setFile('source/util.bs', ` + sub test() + myVar = "hello" ' setting type to string + myVar = myVar.trim() + print 1; myVar + myVar = "hello".len() ' setting type to integer + myVar = sqr(33) ' setting type to float + print 2; myVar + end sub + `); + + const expectedHoverStr = `myVar as string or integer or float`; + + program.validate(); + expectZeroDiagnostics(program); + // print 1; my|Var + let hover = program.getHover(file.srcPath, util.createPosition(4, 31))[0]; + expect(hover?.contents).eql([fence(expectedHoverStr)]); + + // my|Var = "hello".len() + hover = program.getHover(file.srcPath, util.createPosition(5, 23))[0]; + expect(hover?.contents).eql([fence(expectedHoverStr)]); + + // print 2; my|Var + hover = program.getHover(file.srcPath, util.createPosition(7, 31))[0]; + expect(hover?.contents).eql([fence(expectedHoverStr)]); + }); + + it('reusing same variable for multiple types', () => { + const file = program.setFile('source/util.bs', ` + namespace stringUtil + function pad(x as string or integer) as string + return "0"+x.toStr() + end function + end namespace + + ' Formats a timestamp into a user friendly string. + ' @param {Integer} time - The unix time stamp to format. + ' @param {String} meridiemStyle - A style key to be wrapped around the meridiem for MultiStyleLabels. + ' @return {String} - The formatted time + function formatTime(time as integer, meridiemStyle = "" as string) as string + dateObj = createObject("roDateTime") + deviceInfo = createObject("roDeviceInfo") + dateObj.fromSeconds(time) + hour = dateObj.getHours() + minutes = dateObj.getMinutes() + + ' Get the Meridiem value + if hour > 11 then + meridiem = "pm" + else + meridiem = "am" + end if + + if meridiemStyle <> "" then + meridiem = "<" + meridiemStyle + ">" + meridiem + "" + end if + + minutes = stringUtil.pad(minutes) + + if deviceInfo.getClockFormat() = "24h" then + hour = stringUtil.pad(hour) + ' "22:01" | "01:01" + return substitute("{0}:{1}", hour, minutes as string) + else + hour = hour mod 12 + if hour = 0 then hour = 12 + ' "10:01 AM" | "1:01 AM" + return substitute("{0}:{1} {2}", hour, minutes as string, meridiem) + end if + end function + `); + const expectedHourHoverStr = `hour as dynamic`; + + program.validate(); + expectZeroDiagnostics(program); + // ho|ur = stringUtil.pad(hour) + let hover = program.getHover(file.srcPath, util.createPosition(32, 27))[0]; + expect(hover?.contents).eql([fence(expectedHourHoverStr)]); + + // ho|ur = hour mod 12 + hover = program.getHover(file.srcPath, util.createPosition(36, 27))[0]; + expect(hover?.contents).eql([fence(expectedHourHoverStr)]); + }); + + }); }); diff --git a/src/bscPlugin/hover/HoverProcessor.ts b/src/bscPlugin/hover/HoverProcessor.ts index bb29ac518..25de123c7 100644 --- a/src/bscPlugin/hover/HoverProcessor.ts +++ b/src/bscPlugin/hover/HoverProcessor.ts @@ -1,4 +1,4 @@ -import { isBrsFile, isCallfuncExpression, isClassStatement, isEnumMemberStatement, isEnumStatement, isEnumType, isInheritableType, isInterfaceStatement, isMemberField, isNamespaceStatement, isNamespaceType, isNewExpression, isTypedFunctionType, isXmlFile } from '../../astUtils/reflection'; +import { isAssignmentStatement, isBrsFile, isCallfuncExpression, isClassStatement, isEnumMemberStatement, isEnumStatement, isEnumType, isInheritableType, isInterfaceStatement, isMemberField, isNamespaceStatement, isNamespaceType, isNewExpression, isTypedFunctionType, isXmlFile } from '../../astUtils/reflection'; import type { BrsFile } from '../../files/BrsFile'; import type { XmlFile } from '../../files/XmlFile'; import type { ExtraSymbolData, Hover, ProvideHoverEvent, TypeChainEntry } from '../../interfaces'; @@ -155,7 +155,12 @@ export class HoverProcessor { const typeFlag = isInTypeExpression ? SymbolTypeFlag.typetime : SymbolTypeFlag.runtime; const typeChain: TypeChainEntry[] = []; const extraData = {} as ExtraSymbolData; - const exprType = expression.getType({ flags: typeFlag, typeChain: typeChain, data: extraData, ignoreCall: isCallfuncExpression(expression) }); + let exprType = expression.getType({ flags: typeFlag, typeChain: typeChain, data: extraData, ignoreCall: isCallfuncExpression(expression) }); + + if (isAssignmentStatement(expression) && token === expression.tokens.name) { + // if this is an assignment, but we're really interested in the LHS, need to a symbol lookup + exprType = expression.getSymbolTable().getSymbolType(expression.tokens.name.text, { flags: typeFlag, typeChain: typeChain, data: extraData }); + } const processedTypeChain = util.processTypeChain(typeChain); const fullName = processedTypeChain.fullNameOfItem || token.text; From 6878b50bd968f5d8efc9b5dc8e35227b38a2282f Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Fri, 10 May 2024 09:15:22 -0300 Subject: [PATCH 167/460] Fixes union type issues with unknown members (#1170) * Hovers on LHS of assignments do symbol type lookups * Fixes when a member that only exsits on one type of union is accessed * Makes 'CannotFindName' diagnostic nicer by tellying you what it can not find the name in * Update src/interfaces.ts Co-authored-by: Bronley Plumb * Update src/interfaces.ts Co-authored-by: Bronley Plumb * fixed #1167 --------- Co-authored-by: Bronley Plumb --- src/DiagnosticMessages.ts | 9 +- src/Scope.spec.ts | 18 ++-- .../completions/CompletionsProcessor.spec.ts | 19 ++++ .../completions/CompletionsProcessor.ts | 9 +- src/bscPlugin/hover/HoverProcessor.spec.ts | 25 +++++- src/bscPlugin/hover/HoverProcessor.ts | 14 ++- src/bscPlugin/validation/BrsFileValidator.ts | 19 ++-- .../validation/ScopeValidator.spec.ts | 61 +++++++++++-- src/bscPlugin/validation/ScopeValidator.ts | 90 ++++++++++++++----- src/files/BrsFile.Class.spec.ts | 6 +- src/interfaces.ts | 9 ++ src/parser/Expression.ts | 2 +- src/types/UnionType.ts | 8 +- src/util.ts | 30 ++++++- 14 files changed, 254 insertions(+), 65 deletions(-) diff --git a/src/DiagnosticMessages.ts b/src/DiagnosticMessages.ts index dfd95c881..0b8c78018 100644 --- a/src/DiagnosticMessages.ts +++ b/src/DiagnosticMessages.ts @@ -19,13 +19,16 @@ export let DiagnosticMessages = { * * @param name for local vars, it's the var name. for namespaced parts, it's the specific part that's unknown (`alpha.beta.charlie` would result in "cannot find name 'charlie') * @param fullName if a namespaced name, this is the full name `alpha.beta.charlie`, otherwise it's the same as `name` + * @param typeName if 'name' refers to a member, what is the the type it is a member of? + * @param typeDescriptor defaults to 'type' ... could also be 'namespace', etc. */ - cannotFindName: (name: string, fullName?: string) => ({ - message: `Cannot find name '${name}'`, + cannotFindName: (name: string, fullName?: string, typeName?: string, typeDescriptor = 'type') => ({ + message: `Cannot find name '${name}'${typeName ? ` for ${typeDescriptor} '${typeName}'` : ''}`, code: 1001, data: { name: name, - fullName: fullName ?? name + fullName: fullName ?? name, + typeName: typeName ? typeName : undefined }, severity: DiagnosticSeverity.Error }), diff --git a/src/Scope.spec.ts b/src/Scope.spec.ts index 6bbfb903e..5f3c58e21 100644 --- a/src/Scope.spec.ts +++ b/src/Scope.spec.ts @@ -371,7 +371,7 @@ describe('Scope', () => { program.validate(); expectDiagnostics(program, [ { - message: DiagnosticMessages.cannotFindName('delta').message, + message: DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace').message, file: { srcPath: buttonPrimary.srcPath }, @@ -379,7 +379,7 @@ describe('Scope', () => { message: `In component scope 'ButtonPrimary'` }] }, { - message: DiagnosticMessages.cannotFindName('delta').message, + message: DiagnosticMessages.cannotFindName('delta', 'constants.alpha.delta', 'constants.alpha', 'namespace').message, file: { srcPath: buttonSecondary.srcPath }, @@ -539,7 +539,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [{ - ...DiagnosticMessages.cannotFindName('subname', 'Name1.subname') + ...DiagnosticMessages.cannotFindName('subname', 'Name1.subname', 'Name1', 'namespace') }]); }); @@ -1755,7 +1755,7 @@ describe('Scope', () => { program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('UnknownType').message + DiagnosticMessages.cannotFindName('UnknownType', 'MyNamespace.UnknownType', 'MyNamespace', 'namespace').message ]); expect(program.getDiagnostics()[0]?.data?.fullName).to.eq('MyNamespace.UnknownType'); }); @@ -1905,7 +1905,7 @@ describe('Scope', () => { program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('NSDoesNotExistC', 'NSExistsA.NSExistsB.NSDoesNotExistC').message + DiagnosticMessages.cannotFindName('NSDoesNotExistC', 'NSExistsA.NSExistsB.NSDoesNotExistC', 'NSExistsA.NSExistsB', 'namespace').message ]); }); @@ -2101,7 +2101,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('someProp').message + DiagnosticMessages.cannotFindName('someProp', 'float.someProp', 'float').message ]); }); @@ -2119,7 +2119,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('noMethod').message + DiagnosticMessages.cannotFindName('noMethod', 'float.noMethod', 'float').message ]); }); @@ -2176,7 +2176,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('name').message + DiagnosticMessages.cannotFindName('name', 'iface.name', 'iFace').message ]); }); @@ -2707,7 +2707,7 @@ describe('Scope', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('legs').message + DiagnosticMessages.cannotFindName('legs', '(Person or Pet).legs', '(Person or Pet)').message ]); const mainFnScope = mainFile.getFunctionScopeAtPosition(util.createPosition(2, 24)); const sourceScope = program.getScopeByName('source'); diff --git a/src/bscPlugin/completions/CompletionsProcessor.spec.ts b/src/bscPlugin/completions/CompletionsProcessor.spec.ts index d5efebc9b..a70482e2b 100644 --- a/src/bscPlugin/completions/CompletionsProcessor.spec.ts +++ b/src/bscPlugin/completions/CompletionsProcessor.spec.ts @@ -1059,6 +1059,25 @@ describe('CompletionsProcessor', () => { }]); }); + it('includes function parameters at the end of the function in namespace', () => { + program.setFile('source/main.bs', ` + namespace alpha + function main(param) + print + myValue = 234 + end function + end namespace + `); + + program.validate(); + // print | + let completions = program.getCompletions(`${rootDir}/source/main.bs`, Position.create(3, 33)); + expectCompletionsIncludes(completions, [{ + label: 'param', + kind: CompletionItemKind.Variable + }]); + }); + it('treats class name as a function', () => { program.setFile('source/main.bs', ` class SomeKlass diff --git a/src/bscPlugin/completions/CompletionsProcessor.ts b/src/bscPlugin/completions/CompletionsProcessor.ts index d030158e1..3eef56a69 100644 --- a/src/bscPlugin/completions/CompletionsProcessor.ts +++ b/src/bscPlugin/completions/CompletionsProcessor.ts @@ -1,4 +1,4 @@ -import { isBlock, isBrsFile, isCallableType, isClassStatement, isClassType, isComponentType, isConstStatement, isEnumMemberType, isEnumType, isFunctionExpression, isInterfaceType, isMethodStatement, isNamespaceStatement, isNamespaceType, isNativeType, isTypedFunctionType, isXmlFile, isXmlScope } from '../../astUtils/reflection'; +import { isBrsFile, isCallableType, isClassStatement, isClassType, isComponentType, isConstStatement, isEnumMemberType, isEnumType, isFunctionExpression, isInterfaceType, isMethodStatement, isNamespaceStatement, isNamespaceType, isNativeType, isTypedFunctionType, isXmlFile, isXmlScope } from '../../astUtils/reflection'; import type { FileReference, ProvideCompletionsEvent } from '../../interfaces'; import type { BscFile } from '../../files/BscFile'; import { AllowedTriviaTokens, DeclarableTypes, Keywords, TokenKind } from '../../lexer/TokenKind'; @@ -21,6 +21,7 @@ import type { AstNode } from '../../parser/AstNode'; import type { ClassStatement, FunctionStatement, NamespaceStatement } from '../../parser/Statement'; import type { Token } from '../../lexer/Token'; import { createIdentifier } from '../../astUtils/creators'; +import type { FunctionExpression } from '../../parser/Expression'; export class CompletionsProcessor { constructor( @@ -235,6 +236,7 @@ export class CompletionsProcessor { const containingClassStmt = expression.findAncestor(isClassStatement); const containingNamespace = expression.findAncestor(isNamespaceStatement); const containingNamespaceName = containingNamespace?.getName(ParseMode.BrighterScript); + const containingFunctionExpression = expression.findAncestor(isFunctionExpression); for (const scope of this.event.scopes) { if (tokenKind === TokenKind.StringLiteral || tokenKind === TokenKind.TemplateStringQuasi) { @@ -261,8 +263,9 @@ export class CompletionsProcessor { // get symbols directly from current symbol table and scope if (!gotSymbolsFromThisFile) { currentSymbols = symbolTable?.getOwnSymbols(symbolTableLookupFlag) ?? []; - if (isBlock(expression) && isFunctionExpression(expression.parent)) { - currentSymbols.push(...expression.parent.getSymbolTable().getOwnSymbols(symbolTableLookupFlag)); + + if (containingFunctionExpression) { + currentSymbols.push(...containingFunctionExpression.getSymbolTable().getOwnSymbols(symbolTableLookupFlag)); } gotSymbolsFromThisFile = true; } diff --git a/src/bscPlugin/hover/HoverProcessor.spec.ts b/src/bscPlugin/hover/HoverProcessor.spec.ts index 0626f2c81..0e7239c99 100644 --- a/src/bscPlugin/hover/HoverProcessor.spec.ts +++ b/src/bscPlugin/hover/HoverProcessor.spec.ts @@ -649,6 +649,23 @@ describe('HoverProcessor', () => { hover = program.getHover(file.srcPath, util.createPosition(3, 38))[0]; expect(hover?.contents).to.be.undefined; }); + + it('should show unresolved members as invalid', () => { + const file = program.setFile('source/main.bs', ` + interface MyIFace + name as string + end interface + + sub doSomething(thing as MyIFace) + print thing.member + end sub + `); + program.validate(); + + // print thing.mem|ber + let hover = program.getHover(file.srcPath, util.createPosition(6, 40))[0]; + expect(hover?.contents).eql([fence('MyIFace.member as invalid')]); + }); }); describe('callFunc', () => { @@ -679,6 +696,7 @@ describe('HoverProcessor', () => { let hover = program.getHover(file.srcPath, util.createPosition(3, 35))[0]; expect(hover?.contents).eql([fence('function roSGNodeWidget@.someFunc(input as string) as float')]); }); + }); describe('multiple definition locations', () => { @@ -687,7 +705,6 @@ describe('HoverProcessor', () => { const file = program.setFile('source/util.bs', ` sub test() myVar = "hello" ' setting type to string - myVar = myVar.trim() print 1; myVar myVar = "hello".len() ' setting type to integer myVar = sqr(33) ' setting type to float @@ -700,15 +717,15 @@ describe('HoverProcessor', () => { program.validate(); expectZeroDiagnostics(program); // print 1; my|Var - let hover = program.getHover(file.srcPath, util.createPosition(4, 31))[0]; + let hover = program.getHover(file.srcPath, util.createPosition(3, 31))[0]; expect(hover?.contents).eql([fence(expectedHoverStr)]); // my|Var = "hello".len() - hover = program.getHover(file.srcPath, util.createPosition(5, 23))[0]; + hover = program.getHover(file.srcPath, util.createPosition(4, 23))[0]; expect(hover?.contents).eql([fence(expectedHoverStr)]); // print 2; my|Var - hover = program.getHover(file.srcPath, util.createPosition(7, 31))[0]; + hover = program.getHover(file.srcPath, util.createPosition(6, 31))[0]; expect(hover?.contents).eql([fence(expectedHoverStr)]); }); diff --git a/src/bscPlugin/hover/HoverProcessor.ts b/src/bscPlugin/hover/HoverProcessor.ts index 25de123c7..b1efd1c28 100644 --- a/src/bscPlugin/hover/HoverProcessor.ts +++ b/src/bscPlugin/hover/HoverProcessor.ts @@ -1,4 +1,4 @@ -import { isAssignmentStatement, isBrsFile, isCallfuncExpression, isClassStatement, isEnumMemberStatement, isEnumStatement, isEnumType, isInheritableType, isInterfaceStatement, isMemberField, isNamespaceStatement, isNamespaceType, isNewExpression, isTypedFunctionType, isXmlFile } from '../../astUtils/reflection'; +import { isAssignmentStatement, isBrsFile, isCallfuncExpression, isClassStatement, isDottedGetExpression, isEnumMemberStatement, isEnumStatement, isEnumType, isInheritableType, isInterfaceStatement, isMemberField, isNamespaceStatement, isNamespaceType, isNewExpression, isTypedFunctionType, isXmlFile } from '../../astUtils/reflection'; import type { BrsFile } from '../../files/BrsFile'; import type { XmlFile } from '../../files/XmlFile'; import type { ExtraSymbolData, Hover, ProvideHoverEvent, TypeChainEntry } from '../../interfaces'; @@ -119,8 +119,8 @@ export class HoverProcessor { private getMemberHover(memberExpression: FieldStatement | InterfaceFieldStatement, expressionType: BscType) { let nameText = `${(memberExpression.parent as ClassStatement | InterfaceStatement)?.getName(ParseMode.BrighterScript)}.${memberExpression.tokens.name.text}`; - let exprTypeString = expressionType.toString(); - const innerText = `${nameText} as ${exprTypeString} `.trim(); + let exprTypeString = expressionType.isResolvable() ? expressionType.toString() : 'invalid'; + const innerText = `${nameText} as ${exprTypeString}`.trim(); let result = fence(innerText); return result; } @@ -185,7 +185,13 @@ export class HoverProcessor { if (isTypedFunctionType(exprType)) { exprType.setName(exprNameString); } - hoverContent = fence(`${variableName}${exprType.toString()}`); + let exprTypeString = exprType.toString(); + if (!exprType.isResolvable()) { + if (isDottedGetExpression(expression)) { + exprTypeString = 'invalid'; + } + } + hoverContent = fence(`${variableName}${exprTypeString}`); } const modifiers = []; // eslint-disable-next-line no-bitwise diff --git a/src/bscPlugin/validation/BrsFileValidator.ts b/src/bscPlugin/validation/BrsFileValidator.ts index 8160d24b3..9c38f2c15 100644 --- a/src/bscPlugin/validation/BrsFileValidator.ts +++ b/src/bscPlugin/validation/BrsFileValidator.ts @@ -51,7 +51,7 @@ export class BrsFileValidator { if (isClassStatement(node.parent) && node.parent.hasParentClass()) { const data: ExtraSymbolData = {}; const parentClassType = node.parent.parentClassName.getType({ flags: SymbolTypeFlag.typetime, data: data }); - node.func.body.symbolTable.addSymbol('super', data, parentClassType, SymbolTypeFlag.runtime); + node.func.body.getSymbolTable().addSymbol('super', data, parentClassType, SymbolTypeFlag.runtime); } }, CallfuncExpression: (node) => { @@ -141,17 +141,24 @@ export class BrsFileValidator { } }, FunctionExpression: (node) => { - if (!node.symbolTable.hasSymbol('m', SymbolTypeFlag.runtime) || node.findAncestor(isAALiteralExpression)) { - node.symbolTable.addSymbol('m', undefined, new AssociativeArrayType(), SymbolTypeFlag.runtime); + const funcSymbolTable = node.getSymbolTable(); + if (!funcSymbolTable?.hasSymbol('m', SymbolTypeFlag.runtime) || node.findAncestor(isAALiteralExpression)) { + if (!isTypecastStatement(node.body?.statements?.[0])) { + funcSymbolTable?.addSymbol('m', undefined, new AssociativeArrayType(), SymbolTypeFlag.runtime); + } } this.validateFunctionParameterCount(node); }, FunctionParameterExpression: (node) => { const paramName = node.tokens.name?.text; - // add param symbol at expression level, so it can be used as default value in other params - const symbolTable = node.findAncestor(isFunctionExpression)?.getSymbolTable(); const nodeType = node.getType({ flags: SymbolTypeFlag.typetime }); - symbolTable?.addSymbol(paramName, { definingNode: node }, nodeType, SymbolTypeFlag.runtime); + // add param symbol at expression level, so it can be used as default value in other params + const funcExpr = node.findAncestor(isFunctionExpression); + const funcSymbolTable = funcExpr?.getSymbolTable(); + funcSymbolTable?.addSymbol(paramName, { definingNode: node }, nodeType, SymbolTypeFlag.runtime); + + //also add param symbol at block level, as it may be redefined, and if so, should show a union + funcExpr.body.getSymbolTable()?.addSymbol(paramName, { definingNode: node }, nodeType, SymbolTypeFlag.runtime); }, InterfaceStatement: (node) => { this.validateDeclarationLocations(node, 'interface', () => util.createBoundingRange(node.tokens.interface, node.tokens.name)); diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts index 0873a334e..9e96b9a57 100644 --- a/src/bscPlugin/validation/ScopeValidator.spec.ts +++ b/src/bscPlugin/validation/ScopeValidator.spec.ts @@ -1571,7 +1571,7 @@ describe('ScopeValidator', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('unknown', 'Klass.unknown') + DiagnosticMessages.cannotFindName('unknown', 'Klass.unknown', 'Klass') ]); }); @@ -1584,7 +1584,7 @@ describe('ScopeValidator', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('length', 'string.length') + DiagnosticMessages.cannotFindName('length', 'string.length', 'string') ]); }); @@ -1708,6 +1708,55 @@ describe('ScopeValidator', () => { DiagnosticMessages.cannotFindName('paramX').message ]); }); + + it('has diagnostic when function default params reference variable from inside function', () => { + program.setFile('source/main.bs', ` + function test(param1 as integer, param2 = paramX + 2) + paramX = 3 + print param1; param2 + end function + `); + program.validate(); + expectDiagnostics(program, [ + DiagnosticMessages.cannotFindName('paramX').message + ]); + }); + + it('has diagnostic when trying to use a method on an union that does not exist in one type', () => { + program.setFile('source/main.bs', ` + function typeHoverTest(x as string or integer) + value = x.len() + return value + end function + `); + program.validate(); + expectDiagnostics(program, [ + DiagnosticMessages.cannotFindName('len', null, '(string or integer)').message + ]); + }); + + it('does not have diagnostic when accessing unknown member of union in Brightscript mode, when variable is a param', () => { + program.setFile('source/main.brs', ` + function typeHoverTest(x as string) + x = x.len() + return x + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); + + it('does not have diagnostic when accessing unknown member of union in Brightscript mode, when variable is defined in block', () => { + program.setFile('source/main.brs', ` + function typeHoverTest() + x = "hello" + x = x.len() + return x + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); }); describe('itemCannotBeUsedAsVariable', () => { @@ -1799,7 +1848,7 @@ describe('ScopeValidator', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('name', 'function.name') + DiagnosticMessages.cannotFindName('name', 'function.name', 'function') ]); }); @@ -1817,7 +1866,7 @@ describe('ScopeValidator', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('name', 'function.name') + DiagnosticMessages.cannotFindName('name', 'function.name', 'function') ]); }); @@ -2215,7 +2264,7 @@ describe('ScopeValidator', () => { end class `); program.validate(); - expectDiagnostics(program, [DiagnosticMessages.cannotFindName('getPi', 'Thing.getPi')]); + expectDiagnostics(program, [DiagnosticMessages.cannotFindName('getPi', 'Thing.getPi', 'Thing')]); }); it('validates class constructors', () => { @@ -2748,7 +2797,7 @@ describe('ScopeValidator', () => { end namespace `); program.validate(); - expectDiagnosticsIncludes(program, [DiagnosticMessages.cannotFindName('SomeEnum').message]); + expectDiagnosticsIncludes(program, [DiagnosticMessages.cannotFindName('SomeEnum', null, 'Alpha.Beta.Charlie', 'namespace').message]); }); it('revalidates when a class defined in a different namespace changes', () => { diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts index 70f2b0a58..d43cb269f 100644 --- a/src/bscPlugin/validation/ScopeValidator.ts +++ b/src/bscPlugin/validation/ScopeValidator.ts @@ -5,7 +5,7 @@ import { Cache } from '../../Cache'; import type { DiagnosticInfo } from '../../DiagnosticMessages'; import { DiagnosticMessages } from '../../DiagnosticMessages'; import type { BrsFile } from '../../files/BrsFile'; -import type { BsDiagnostic, CallableContainer, ExtraSymbolData, FileReference, OnScopeValidateEvent, TypeChainEntry, TypeCompatibilityData } from '../../interfaces'; +import type { BsDiagnostic, CallableContainer, ExtraSymbolData, FileReference, GetTypeOptions, OnScopeValidateEvent, TypeChainEntry, TypeChainProcessResult, TypeCompatibilityData } from '../../interfaces'; import { SymbolTypeFlag } from '../../SymbolTypeFlag'; import type { AssignmentStatement, ClassStatement, DottedSetStatement, EnumStatement, NamespaceStatement, ReturnStatement } from '../../parser/Statement'; import util from '../../util'; @@ -28,6 +28,8 @@ import { globalCallableMap } from '../../globalCallables'; import type { XmlScope } from '../../XmlScope'; import type { XmlFile } from '../../files/XmlFile'; import { SGFieldTypes } from '../../parser/SGTypes'; +import { DynamicType } from '../../types'; +import { BscTypeKind } from '../../types/BscTypeKind'; /** * The lower-case names of all platform-included scenegraph nodes @@ -144,7 +146,7 @@ export class ScopeValidator { // Note: this also includes For statements this.detectShadowedLocalVar(file, { name: assignStmt.tokens.name.text, - type: assignStmt.getType({ flags: SymbolTypeFlag.runtime }), + type: this.getNodeTypeWrapper(file, assignStmt, { flags: SymbolTypeFlag.runtime }), nameRange: assignStmt.tokens.name.range }); }, @@ -154,14 +156,14 @@ export class ScopeValidator { ForEachStatement: (forEachStmt) => { this.detectShadowedLocalVar(file, { name: forEachStmt.tokens.item.text, - type: forEachStmt.getType({ flags: SymbolTypeFlag.runtime }), + type: this.getNodeTypeWrapper(file, forEachStmt, { flags: SymbolTypeFlag.runtime }), nameRange: forEachStmt.tokens.item.range }); }, FunctionParameterExpression: (funcParam) => { this.detectShadowedLocalVar(file, { name: funcParam.tokens.name.text, - type: funcParam.getType({ flags: SymbolTypeFlag.runtime }), + type: this.getNodeTypeWrapper(file, funcParam, { flags: SymbolTypeFlag.runtime }), nameRange: funcParam.tokens.name.range }); } @@ -397,7 +399,7 @@ export class ScopeValidator { */ private validateFunctionCall(file: BrsFile, expression: CallExpression) { const getTypeOptions = { flags: SymbolTypeFlag.runtime, data: {} }; - let funcType = expression?.callee?.getType(getTypeOptions); + let funcType = this.getNodeTypeWrapper(file, expression?.callee, getTypeOptions); if (funcType?.isResolvable() && isClassType(funcType)) { // We're calling a class - get the constructor funcType = funcType.getMemberType('new', getTypeOptions); @@ -433,7 +435,7 @@ export class ScopeValidator { let paramIndex = 0; for (let arg of expression.args) { const data = {} as ExtraSymbolData; - let argType = arg.getType({ flags: SymbolTypeFlag.runtime, data: data }); + let argType = this.getNodeTypeWrapper(file, arg, { flags: SymbolTypeFlag.runtime, data: data }); const paramType = funcType.params[paramIndex]?.type; if (!paramType) { @@ -472,7 +474,7 @@ export class ScopeValidator { const getTypeOptions = { flags: SymbolTypeFlag.runtime }; let funcType = returnStmt.findAncestor(isFunctionExpression).getType({ flags: SymbolTypeFlag.typetime }); if (isTypedFunctionType(funcType)) { - const actualReturnType = returnStmt.value?.getType(getTypeOptions); + const actualReturnType = this.getNodeTypeWrapper(file, returnStmt?.value, getTypeOptions); const compatibilityData: TypeCompatibilityData = {}; if (actualReturnType && !funcType.returnType.isTypeCompatible(actualReturnType, compatibilityData)) { @@ -487,14 +489,14 @@ export class ScopeValidator { } /** - * Detect return statements with incompatible types vs. declared return type + * Detect assigned type different from expected member type */ private validateDottedSetStatement(file: BrsFile, dottedSetStmt: DottedSetStatement) { const typeChainExpectedLHS = [] as TypeChainEntry[]; const getTypeOpts = { flags: SymbolTypeFlag.runtime }; - const expectedLHSType = dottedSetStmt?.getType({ ...getTypeOpts, data: {}, typeChain: typeChainExpectedLHS }); - const actualRHSType = dottedSetStmt?.value?.getType(getTypeOpts); + const expectedLHSType = this.getNodeTypeWrapper(file, dottedSetStmt, { ...getTypeOpts, data: {}, typeChain: typeChainExpectedLHS }); + const actualRHSType = this.getNodeTypeWrapper(file, dottedSetStmt?.value, getTypeOpts); const compatibilityData: TypeCompatibilityData = {}; const typeChainScan = util.processTypeChain(typeChainExpectedLHS); // check if anything in typeChain is an AA - if so, just allow it @@ -506,7 +508,7 @@ export class ScopeValidator { if (!expectedLHSType?.isResolvable()) { this.addMultiScopeDiagnostic({ file: file as BscFile, - ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem), + ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem, typeChainScan.itemParentTypeName, this.getParentTypeDescriptor(typeChainScan)), range: typeChainScan.range }); return; @@ -541,8 +543,8 @@ export class ScopeValidator { const typeChainExpectedLHS = []; const getTypeOpts = { flags: SymbolTypeFlag.runtime }; - const expectedLHSType = assignStmt.typeExpression.getType({ ...getTypeOpts, data: {}, typeChain: typeChainExpectedLHS }); - const actualRHSType = assignStmt.value?.getType(getTypeOpts); + const expectedLHSType = this.getNodeTypeWrapper(file, assignStmt.typeExpression, { ...getTypeOpts, data: {}, typeChain: typeChainExpectedLHS }); + const actualRHSType = this.getNodeTypeWrapper(file, assignStmt.value, getTypeOpts); const compatibilityData: TypeCompatibilityData = {}; if (!expectedLHSType || !expectedLHSType.isResolvable()) { this.addMultiScopeDiagnostic({ @@ -569,8 +571,8 @@ export class ScopeValidator { return; } - let leftType = binaryExpr.left.getType(getTypeOpts); - let rightType = binaryExpr.right.getType(getTypeOpts); + let leftType = this.getNodeTypeWrapper(file, binaryExpr.left, getTypeOpts); + let rightType = this.getNodeTypeWrapper(file, binaryExpr.right, getTypeOpts); if (!leftType.isResolvable() || !rightType.isResolvable()) { // Can not find the type. error handled elsewhere @@ -622,7 +624,7 @@ export class ScopeValidator { private validateUnaryExpression(file: BrsFile, unaryExpr: UnaryExpression) { const getTypeOpts = { flags: SymbolTypeFlag.runtime }; - let rightType = unaryExpr.right.getType(getTypeOpts); + let rightType = this.getNodeTypeWrapper(file, unaryExpr.right, getTypeOpts); if (!rightType.isResolvable()) { // Can not find the type. error handled elsewhere @@ -688,7 +690,7 @@ export class ScopeValidator { // this will create a diagnostic if an invalid member is accessed const typeChain: TypeChainEntry[] = []; const typeData = {} as ExtraSymbolData; - let exprType = expression.getType({ + let exprType = this.getNodeTypeWrapper(file, expression, { flags: symbolType, typeChain: typeChain, data: typeData @@ -697,9 +699,9 @@ export class ScopeValidator { const hasValidDeclaration = this.hasValidDeclaration(expression, exprType, typeData?.definingNode); if (!this.isTypeKnown(exprType) && !hasValidDeclaration) { - if (expression.getType({ flags: oppositeSymbolType, isExistenceTest: true })?.isResolvable()) { + if (this.getNodeTypeWrapper(file, expression, { flags: oppositeSymbolType, isExistenceTest: true })?.isResolvable()) { const oppoSiteTypeChain = []; - const invalidlyUsedResolvedType = expression.getType({ flags: oppositeSymbolType, typeChain: oppoSiteTypeChain, isExistenceTest: true }); + const invalidlyUsedResolvedType = this.getNodeTypeWrapper(file, expression, { flags: oppositeSymbolType, typeChain: oppoSiteTypeChain, isExistenceTest: true }); const typeChainScan = util.processTypeChain(oppoSiteTypeChain); if (isUsedAsType) { this.addMultiScopeDiagnostic({ @@ -720,7 +722,7 @@ export class ScopeValidator { const typeChainScan = util.processTypeChain(typeChain); this.addMultiScopeDiagnostic({ file: file as BscFile, - ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem), + ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem, typeChainScan.itemParentTypeName, this.getParentTypeDescriptor(typeChainScan)), range: typeChainScan.range }); } @@ -729,7 +731,7 @@ export class ScopeValidator { const typeChainScan = util.processTypeChain(typeChain); this.addMultiScopeDiagnostic({ file: file as BscFile, - ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem), + ...DiagnosticMessages.cannotFindName(typeChainScan.itemName, typeChainScan.fullNameOfItem, typeChainScan.itemParentTypeName, this.getParentTypeDescriptor(typeChainScan)), range: typeChainScan.range }); } @@ -880,7 +882,7 @@ export class ScopeValidator { * and make sure we can find a class with that name */ private validateNewExpression(file: BrsFile, newExpression: NewExpression) { - const newExprType = newExpression.getType({ flags: SymbolTypeFlag.typetime }); + const newExprType = this.getNodeTypeWrapper(file, newExpression, { flags: SymbolTypeFlag.typetime }); if (isClassType(newExprType)) { return; } @@ -1358,6 +1360,50 @@ export class ScopeValidator { } } + /** + * Wraps the AstNode.getType() method, so that we can do extra processing on the result based on the current file + * In particular, since BrightScript does not support Unions, and there's no way to cast them to something else + * if the result of .getType() is a union, and we're in a .brs (brightScript) file, treat the result as Dynamic + * + * In most cases, this returns the result of node.getType() + * + * @param file the current file being processed + * @param node the node to get the type of + * @param getTypeOpts any options to pass to node.getType() + * @returns the processed result type + */ + private getNodeTypeWrapper(file: BrsFile, node: AstNode, getTypeOpts: GetTypeOptions) { + const type = node?.getType(getTypeOpts); + + if (file.parseMode === ParseMode.BrightScript) { + // this is a brightscript file + const typeChain = getTypeOpts.typeChain; + if (typeChain) { + const hasUnion = typeChain.reduce((hasUnion, tce) => { + return hasUnion || isUnionType(tce.type); + }, false); + if (hasUnion) { + // there was a union somewhere in the typechain + return DynamicType.instance; + } + } + if (isUnionType(type)) { + //this is a union + return DynamicType.instance; + } + } + + // by default return the result of node.getType() + return type; + } + + private getParentTypeDescriptor(typeChainResult: TypeChainProcessResult) { + if (typeChainResult.itemParentTypeKind === BscTypeKind.NamespaceType) { + return 'namespace'; + } + return 'type'; + } + private addDiagnostic(diagnostic: BsDiagnostic) { this.event.program.diagnostics.register(diagnostic, { tags: [ScopeValidatorDiagnosticTag], diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts index 20fedfd76..7ff1be10f 100644 --- a/src/files/BrsFile.Class.spec.ts +++ b/src/files/BrsFile.Class.spec.ts @@ -1560,7 +1560,7 @@ describe('BrsFile BrighterScript classes', () => { `); program.validate(); expectDiagnostics(program, [{ - ...DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird'), + ...DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird', 'Vertibrates', 'namespace'), relatedInformation: [{ message: `In scope 'source'` }] @@ -1584,7 +1584,7 @@ describe('BrsFile BrighterScript classes', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird').message + DiagnosticMessages.cannotFindName('GroundedBird', 'Vertibrates.GroundedBird', 'Vertibrates', 'namespace').message ]); }); }); @@ -1632,7 +1632,7 @@ describe('BrsFile BrighterScript classes', () => { `); program.validate(); expectDiagnostics(program, [ - DiagnosticMessages.cannotFindName('AnimalNotDefined', 'NameA.NameB.AnimalNotDefined') + DiagnosticMessages.cannotFindName('AnimalNotDefined', 'NameA.NameB.AnimalNotDefined', 'NameA.NameB', 'namespace') ]); }); diff --git a/src/interfaces.ts b/src/interfaces.ts index 7b0c80cdc..635fa500b 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -20,6 +20,7 @@ import type { SymbolTable } from './SymbolTable'; import type { SymbolTypeFlag } from './SymbolTypeFlag'; import { createToken } from './astUtils/creators'; import { TokenKind } from './lexer/TokenKind'; +import type { BscTypeKind } from './types/BscTypeKind'; export interface BsDiagnostic extends Diagnostic { file: BscFile; @@ -902,10 +903,18 @@ export interface TypeChainProcessResult { * The name of the last item in the chain, OR the first unresolved item in the chain */ itemName: string; + /** + * The TypeKind of the item of `itemName` + */ + itemTypeKind: BscTypeKind | string; /** * The name of the parent of the item of `itemName` */ itemParentTypeName: string; + /** + * The TypeKind of the parent of the item of `itemName` + */ + itemParentTypeKind: BscTypeKind | string; /** * The complete chain leading up to the item of `itemName` */ diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts index 87aba33fd..6e1b9c9a2 100644 --- a/src/parser/Expression.ts +++ b/src/parser/Expression.ts @@ -224,7 +224,6 @@ export class FunctionExpression extends Expression implements TypedefProvider { this.parameters = options.parameters ?? []; this.body = options.body; this.returnTypeExpression = options.returnTypeExpression; - //if there's a body, and it doesn't have a SymbolTable, assign one if (this.body) { if (!this.body.symbolTable) { @@ -232,6 +231,7 @@ export class FunctionExpression extends Expression implements TypedefProvider { } else { this.body.symbolTable.pushParentProvider(() => this.getSymbolTable()); } + this.body.parent = this; } this.symbolTable = new SymbolTable('FunctionExpression', () => this.parent?.getSymbolTable()); } diff --git a/src/types/UnionType.ts b/src/types/UnionType.ts index 608fbc389..8b32f04e1 100644 --- a/src/types/UnionType.ts +++ b/src/types/UnionType.ts @@ -41,14 +41,18 @@ export class UnionType extends BscType { getMemberType(name: string, options: GetTypeOptions) { const innerTypesMemberTypes = this.getMemberTypeFromInnerTypes(name, options); - if (!innerTypesMemberTypes) { + if (!innerTypesMemberTypes || innerTypesMemberTypes.includes(undefined)) { // We don't have any members of any inner types that match // so instead, create reference type that will return new ReferenceType(name, name, options.flags, () => { return { name: `UnionType MemberTable: '${this.__identifier}'`, getSymbolType: (innerName: string, innerOptions: GetTypeOptions) => { - return getUniqueType(findTypeUnion(this.getMemberTypeFromInnerTypes(name, options)), unionTypeFactory); + const referenceTypeInnerMemberTypes = this.getMemberTypeFromInnerTypes(name, options); + if (!innerTypesMemberTypes || innerTypesMemberTypes.includes(undefined)) { + return undefined; + } + return getUniqueType(findTypeUnion(referenceTypeInnerMemberTypes), unionTypeFactory); }, setCachedType: (innerName: string, innerCacheEntry: TypeCacheEntry, innerOptions: GetTypeOptions) => { // TODO: is this even cachable? This is a NO-OP for now, and it shouldn't hurt anything diff --git a/src/util.ts b/src/util.ts index b95b62e41..7e5ef3a3f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -26,7 +26,7 @@ import type { CallExpression, CallfuncExpression, DottedGetExpression, FunctionP import { Logger, LogLevel } from './Logger'; import { isToken, type Identifier, type Locatable, type Token } from './lexer/Token'; import { TokenKind } from './lexer/TokenKind'; -import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLiteralString, isLongIntegerType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection'; +import { isAnyReferenceType, isBinaryExpression, isBooleanType, isBrsFile, isCallExpression, isCallableType, isCallfuncExpression, isClassType, isDottedGetExpression, isDoubleType, isDynamicType, isEnumMemberType, isExpression, isFloatType, isIndexedGetExpression, isInvalidType, isLiteralString, isLongIntegerType, isNewExpression, isNumberType, isStringType, isTypeExpression, isTypedArrayExpression, isTypedFunctionType, isUnionType, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection'; import { WalkMode } from './astUtils/visitors'; import { SourceNode } from 'source-map'; import * as requireRelative from 'require-relative'; @@ -1998,6 +1998,8 @@ export class Util { let itemName = ''; let previousTypeName = ''; let parentTypeName = ''; + let itemTypeKind = ''; + let parentTypeKind = ''; let errorRange: Range; let containsDynamic = false; let continueResolvingAllItems = true; @@ -2010,8 +2012,30 @@ export class Util { fullChainName += chainItem.name; if (continueResolvingAllItems) { parentTypeName = previousTypeName; + parentTypeKind = itemTypeKind; fullErrorName = previousTypeName ? `${previousTypeName}${dotSep}${chainItem.name}` : chainItem.name; - previousTypeName = chainItem.type?.toString() ?? ''; + + let typeString = chainItem.type?.toString(); + let typeToFindStringFor = chainItem.type; + while (typeToFindStringFor) { + if (isUnionType(chainItem.type)) { + typeString = `(${typeToFindStringFor.toString()})`; + break; + } else if (isCallableType(typeToFindStringFor)) { + if (isTypedFunctionType(typeToFindStringFor) && i < typeChain.length - 1) { + typeToFindStringFor = typeToFindStringFor.returnType; + } else { + typeString = 'function'; + break; + } + } else { + typeString = typeToFindStringFor?.toString(); + break; + } + } + + previousTypeName = typeString ?? ''; + itemTypeKind = (chainItem.type as any)?.kind; itemName = chainItem.name; containsDynamic = containsDynamic || (isDynamicType(chainItem.type) && !isAnyReferenceType(chainItem.type)); if (!chainItem.isResolved) { @@ -2022,7 +2046,9 @@ export class Util { } return { itemName: itemName, + itemTypeKind: itemTypeKind, itemParentTypeName: parentTypeName, + itemParentTypeKind: parentTypeKind, fullNameOfItem: fullErrorName, fullChainName: fullChainName, range: errorRange, From d4479b45896438c4ed66d18ccaf500fb327c9b4e Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Fri, 10 May 2024 12:37:11 -0300 Subject: [PATCH 168/460] Adds roSGNode Update() overload (#1173) * Fixed a few param names in built in types * Manually added overloaded roSgNode.update method * Re-fetched docs --------- Co-authored-by: Bronley Plumb --- scripts/.cache.json | 2 +- scripts/scrape-roku-docs.ts | 34 ++++++++++++++++++--- src/roku-types/data.json | 61 +++++++++++++++++++------------------ 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/scripts/.cache.json b/scripts/.cache.json index 6386dc93e..228fab78b 100644 --- a/scripts/.cache.json +++ b/scripts/.cache.json @@ -1 +1 @@ -{"https://developer.roku.com/api/v1/get-dev-cms-doc?filePath=left-nav%2Freferences.json&locale=en-us":"{\n \"content\": \"{\\n \\\"Reference overview\\\":\\\"/docs/references/references-overview.md\\\",\\n\\n \\\"SceneGraph\\\":{\\n \\\"Component functions\\\":{\\n \\\"init()\\\":\\\"/docs/references/scenegraph/component-functions/init.md\\\",\\n \\\"onKeyEvent()\\\":\\\"/docs/references/scenegraph/component-functions/onkeyevent.md\\\"\\n },\\n \\\"XML elements\\\":{\\n \\\"\\\":\\\"/docs/references/scenegraph/xml-elements/component.md\\\",\\n \\\"\\\":\\\"/docs/references/scenegraph/xml-elements/interface.md\\\",\\n \\\"\n \n \n \n\t\t\n\t\t\n\t\t\n\n\t\t\t\n\n \n \n \n\t\t\n\t\n\t\n\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\n\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\n","https://developer.roku.com/api/v1/get-dev-cms-doc?locale=en-us&filePath=developer-program/getting-started/architecture/content-metadata.md":"{\n \"content\": \"

Content metadata

\\n

Content metadata describes a viewable title that will be shown to the\\nuser. Content may be any supported type of video and the metadata is\\nused by the UI to format and display the title to the user. Some\\nattributes (e.g. ContentType) affect how the title is displayed on\\nscreen, other attributes (e.g. SDPosterURL) specify where to fetch\\nartwork to display with the content and other attributes (e.g. Title)\\nare just rendered as text.

\\n

Overview

\\n

The content metadata is stored in an associative array by the script\\nand provided to the various screen objects as needed for display. In\\nsome cases an array of content metadata may be provided so that the\\nscreen can render multiple items as a list. The attribute names are\\ncritical and used as the key to look up the attribute at run time. The\\nfollowing table details the attributes in use. Certain attributes are\\nrecognized by particular screens, while others are more globally\\napplicable. If the attribute is not a generally recognized attribute,\\nthe table below specifies where the attributes are recognized.

\\n

Keep in mind that there are two ways to specify stream content metadata,\\ndata.Stream and data.Streams:

\\n
    \\n
  • data.Stream: This is used when there is one stream URL,\\ntypically an HLS or smooth streaming manifest URL.
  • \\n
  • data.Streams: This is used when you have a set of fixed bitrate\\nstreams. This is typically the case for non-adaptive MP4 streams,\\nin which case multiple variants are specified to simulate true\\nadaptation.
  • \\n
\\n

Descriptive attributes

\\n

Descriptive metadata attributes can be used to describe the content\\nitem to the user, in a user interface element that allows the user to\\nselect the item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributesTypeValuesExample
ContentTypeStringAlthough ContentType accepts type String, the return value is of type roInt. See table below. \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
Content TypeReturn Value
audio5
episode4
movie1
not set or not supported0
season3
series2
\\\"movie\\\"
TitleStringContent title: movie title for films; episode title for TV series\\\"The Dark Knight\\\"
TitleSeasonStringSeason title for TV series\\\"Battlestar Galactica Season 5\\\"
SecondaryTitle

Available since Roku OS 11.5
StringSecondary title for the video content\\\"2022\\\" (release year of the movie)
DescriptionStringDescription of content\\\"Batman, Gordon and Harvey Dent are forced…\\\"
SDPosterUrlStringURL for SD content artworkmysite.com/img/sd1932.jpg
HDPosterUrlStringURL for HD content artworkmysite.com/img/hd1932.jpg
FHDPosterUrlStringURL for FHD content artworkmysite.com/img/fhd1932.jpg
ReleaseDateStringFormatted Date String\\\"3/31/2009\\\"
RatingStringSelects an icon to be displayed for the corresponding MPAA or TV rating, that is, the value will display as an icon artwork. See Rating Attribute Icons for a list of the acceptable values and the corresponding icon.\\\"PG-13\\\"
StarRatingIntegerSpecifies the star rating to display as red star icon artwork, as a number from 1 to 100:
    \\n
  • 20 displays one star
  • \\n
  • 40 displays two stars
  • \\n
  • 60 displays three stars
  • \\n
  • 80 displays four stars
  • \\n
  • 100 displays five stars
  • \\n
Numbers not divisible by 20 are displayed as a fractional star (A number of 30 will display one and a half stars)
80
UserStarRatingIntegerSpecifies the user star rating to display as yellow star icon artwork, as a number from 1 to 100:
    \\n
  • 20 displays one star
  • \\n
  • 40 displays two stars
  • \\n
  • 60 displays three stars
  • \\n
  • 80 displays four stars
  • \\n
  • 100 displays five stars
  • \\n
Does not display fractional stars for numbers not divisible by 20
80
ShortDescriptionLine1StringLine 1 of Poster Screen Description\\\"The Dark Knight\\\"
ShortDescriptionLine2StringLine 2 of Poster Screen Description\\\"Rent $1.99, Buy $14.99\\\"
EpisodeNumberStringEpisode Number\\\"1\\\"
NumEpisodesIntegerNumber of episodes for a \\\"season\\\" or \\\"series\\\" contentType40
ActorsroArrayList of Actor Names[\\\"Brad Pitt\\\", \\\"Angelina Jolie\\\"]
ActorsStringIndividual Actor Name\\\"Brad Pitt\\\"
DirectorsroArrayList of Director Names[\\\"Joel Coen\\\", \\\"Clint Eastwood\\\"]
CategoriesroArrayList of Category/Genre Names[\\\"Comedy\\\", \\\"Drama\\\"]
CategoriesStringIndividual Category/Genre Name\\\"Comedy\\\"
AlbumStringroSpringboard audio style uses this to display the album\\\"Achtung\\\"
ArtistStringroSpringboard audio style uses to show artist\\\"U2\\\"
TextOverlayULStringroSlideShow displays this string in Upper Left corner of slide\\\"Joe's Photos\\\"
TextOverlayURStringroSlideShow displays this string in Upper Right corner of slide\\\"3 of 20\\\"
TextOverlayBodyStringroSlideShow displays this string on the bottom part of slide\\\"Wanda's 40'th Birthday\\\"
\\n

Digital rights management (DRM) control attributes

\\n

Digital rights management (DRM) content meta-data control attributes are available in the Roku OS through the drmParams parameter of type roAssociativeArray. The table below enumerates all usable attributes of drmParams.

\\n

Note: Not all attributes are required, and may not have the same semantic meaning when applied to different DRM systems.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeDRM SystemTypeValueExample
appDataPlayready, Widevine, Verimatrix: OptionalStringSpecial meaning per DRM system. If supplied, expected to be a base64 encoded string.\\\"SGF2ZSB0byBkZWFsIHdpdGggQmFzZTY0IGZ...\\\"
encodingKeyPlayready: OptionalStringThis field is deprecated; use the licenseServerURL field.

Specifies the PlayReady license acquisition data, in format depending on the EncodingType attribute value specified:
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionUrl\\\", the EncodingKey attribute contains the PlayReady license acquisition URL
  • \\n
\\\"http://serverName/
encodingTypePlayready: OptionalStringThis field is deprecated; use the licenseServerURL field.

Specifies the encoding scheme for PlayReady DRM, by setting to one of the following values:
    \\n
  • \\\"PlayReadyLicenseAcquisitionUrl\\\"
  • \\n
  • \\\"PlayReadyLicenseAcquisitionAndChallenge\\\" Note, this is the same value that used to be specified directly in Content Metadata structure
  • \\n
\\\"PlayReadyLicenseAcquisitionAndChallenge\\\"
KeySystemRequired for allString\\\"playready\\\" or \\\"widevine\\\". This value is case-insensitive. The default is an empty string.

\\n

As of Roku OS 9.3, support for Verimatrix DRM has been removed from the firmware. Make sure that content in your channel is protected using one of the following Roku-supported DRMs: Microsoft PlayReady or Widevine. Click here for more information on implementing these DRMs.

\\n
\\\"widevine\\\"
licenseRenewURLWidevine: OptionalStringA URL location for sending license renewal requests. If not specified, the Roku OS would send renewal requests to the URL specified in the licenseServerURL.\\\" https://host.com/license/wideivne/renew?licenseid=090495867002 \\\"
licenseServerURLPlayready: Required Widevine: RequiredStringA URL location of a license server. This URL may include CGI parameters.

If this field contains the PlayReady license acquisition URL plus additional custom license acquisition request data in format \\\"URL%%%\\\", the “PlayReadyLicenseAcquisitionAndChallenge\\\" type is used.
\\\"https://host.com/license/playready?contentid=090495867002 \\\"
serializationURLPlayready, Widevine: OptionalStringA server address used for device provisioning\\\"https://host.com/provision/device?esn=090495867002 \\\"
serviceCertWidevine: Optional Others: N/R (leave unset)StringThe actual certificate string for Widevine purposes, which must be obtained out-of-band (OOB) by the channel. Leave this unset unless Widevine is used for DRM.Certificate strings are too long to display here. Examples can be fetched from such sources as the Widevine test license server at \\\"https://proxy.uat.widevine.com/proxy. \\\"
lic_acq_window

Available since Roku OS 10.5
Widevine: OptionalstringThe maximum amount of time (in milliseconds) that a channel waits before rotating its Widevine DRM keys. The channel can generate a random wait time between 0 and the value specified in the lic_acq_window field, and use the random wait time to instruct when the Video node should make its next Widevine license request.1000
\\n
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionAndChallenge\\\", the EncodingKey attribute contains the PlayReady license acquisition URL plus additional custom license acquisition request data in format \\\"URL%%%\\\" Note, this is the same value that used to be specified directly in Content Metadata structure The channel just needs to set drmParams.licenseSererUrl.
  • \\n
\\n

Passing custom HTTP headers to licensing requests

\\n

Developers looking to pass custom HTTP headers with a licensing request can now set those headers using the ifHttpAgent interface methods on the Video node.

\\n

Example of configuring a dash stream with Widevine DRM

\\n
contMeta = {\\n    HDPosterUrl:\\\"pkg:/images/BigBuckBunny.jpg\\\"\\n    SDPosterUrl:\\\"pkg:/images/BigBuckBunny.jpg\\\"\\n    ShortDescriptionLine1:\\\"Parking Wars(VOD)\\\"\\n    ShortDescriptionLine2:\\\"dash | widevine\\\"\\n    Streamformat:\\\"dash\\\"\\n    SwitchingStrategy:\\\"\\\"\\n    MinBandwidth:500\\n    VideoUrl: \\\"http://dev.domain.com/mm/dash/vod/173850/85768039/TG_W_WIFI.mpd\\\"\\n    drmParams: { ' setting up DRM config\\n        keySystem: \\\"Widevine\\\"\\n        licenseServerURL: \\\"http://msfrn-ci-cp-dev.mobitv.com/widevine/get_license\\\"\\n    }\\n}\\n
\\n

Content classification attributes

\\n

Available since Roku OS 13.0

\\n

Developers can use the contentClassifier content metadata attribute to specify the genre of their content (for example, action, sports, or comedy), and the Roku OS will use this attribute to automatically adjust the sound and picture on Roku TVs (if auto mode is selected for the picture or sound settings).

\\n
Content classifier value
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
contentClassiferstring
    \\n
  • \\\" \\\"
  • \\n
  • \\\"action\\\"
  • \\n
  • \\\"animated\\\"
  • \\n
  • \\\"black+white\\\" (black and white)
  • \\n
  • \\\"comedy\\\"
  • \\n
  • \\\"drama\\\"
  • \\n
  • \\\"music\\\"
  • \\n
  • \\\"music:lyrics\\\"
  • \\n
  • \\\"nature\\\"
  • \\n
  • \\\"news\\\"
  • \\n
  • \\\"podcast\\\" (audio only)
  • \\n
  • \\\"reality\\\"
  • \\n
  • \\\"sports\\\"
  • \\n
\\\"drama\\\"
\\n
Content classifier sound and picture modes
\\n

The following table details how the different contentClassifier attribute values are mapped to sound and picture modes on Roku TVs.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
Content ClassifierSound ModePicture Mode
\\\" \\\"StandardStandard
actionMovieMovie
sportsStandardSports
comedyMovieMovie
dramaMovieMovie
musicMusicStandard
music:lyricsMusicLow Power
newsDialogVivid
podcast (Audio Only )DialogLow Power
animatedMovieVivid
black+whiteMovieStandard
natureStandardVivid
realityStandardStandard
\\n

Playback configuration attributes

\\n

Playback configuration meta-data attributes are used to configure the playback of the content item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
LiveBooleanOptional flag indicating video is live. Replaces time remaining in progress bar to display \\\"Live\\\". Default is falseTrue
UrlStringStream URL for Scene Graph Video nodemysite.com/img/vacation.jpg
SDBifUrlStringBIF URL for SD trick modemysite.com/bif/sd1932.bif
HDBifUrlStringBIF URL for HD trick modemysite.com/bif/hd1932.bif
FHDBifUrlStringBIF URL for FHD trick modemysite.com/bif/fhd1932.bif
StreamroAssociativeArraySupported by roVideoPlayer and roVideoScreen, but not the Roku Scene Graph Video node.
For the Video node, use the top level url, streamformat, etc. attributes.

The exception is cases where you don't have adaptive streams (typically MP4) and need to specify different bitrate variants separately. For this use case use the Streams attribute. roAssociativeArray that has parameters representing the stream settings that were set as individual roArrays in previous firmware revisions.

The old method is still supported and descriptions of the parameters can be found under those content-meta data entries.

For url please see StreamUrls, for quality it is now a Boolean that is true for HD quality.
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyType
urlString
stickyredirectsBoolean
qualityBoolean
contentidString
bitrateInteger
{ url : \\\"http://me.com/big.m3u8\\\", quality : true, contentid : \\\"big-hls\\\" }
StreamsroArray of roAssociativeArraysUsed by roVideoPlayer and roVideoScreen to specify the content metadata for a set of fixed bitrate streams.

Each array item specifies the URL, bitrate, etc. for one stream variant.

Setting stream content metadata using the Streams value is recommended for non-adaptive video (such as MP4 progressive download) only.

For adaptive streaming, use the Stream metadata value.
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyType
urlString
stickyredirectsBoolean
qualityBoolean
contentidString
bitrateInteger
[ { url : \\\"http://me.com/x-384.mp4\\\", bitrate : 384, quality : false, contentid : \\\"x-384\\\" }, { url : \\\"http://me.com/x2500.mp4\\\", bitrate : 2500, quality : true, contentid : \\\"x-1500\\\" } ]
StreamBitratesroArrayArray of bitrates in kbps for content streams used.

Setting stream bitrates using this value is recommended for non-adaptive video (such as MP4 progressive download) only.

Must be used in conjunction with StreamUrls and StreamQualities
[ 384, 500, 1000, 1500 ]
StreamUrlsroArrayArray of URLs for content streams.

Setting stream urls using this value is recommended for non-adaptive video (such as MP4 progressive download) only.

Must be used in conjunction with StreamBitrates and StreamQualities
[ \\\"mysite.com/vid/1932-1.mp4\\\", \\\"mysite.com/vid/1932-2.mp4\\\", \\\"mysite.com/vid/1932-3.mp4\\\", \\\"mysite.com/vid/1932-4.mp4\\\" ]
StreamQualitiesroArrayArray of Strings quality indicators identifying a stream as \\\"SD\\\" or \\\"HD\\\".

Must be used in conjunction with StreamBitrates and StreamUrls
[ \\\"SD\\\", \\\"SD\\\", \\\"SD\\\", \\\"HD\\\" ]
StreamContentIDsroArrayarray of strings values logged in Roku logs to identify stream and bitrate played[ \\\"myco-19321-384.mp4\\\", \\\"myco-19321-500.mp4\\\", \\\"myco-19321-1000.mp4\\\", \\\"myco-19321-1500.mp4\\\" ]
StreamStickyHttpRedirectsroArrayArray of Boolean values indicating if the HTTP endpoint should be sticky and not subject to change on subsequent requests. Default is false[ false, false, false, false ]
StreamStartTimeOffsetIntegerOptional. Default is 0. The offset into the stream which is considered the beginning of playback. Time in seconds.3600 (one hour)
StreamFormatStringType of content
    \\n
  • \\n

    Type of content:

    \\n
      \\n
    • Default: H.264/AAC in .mp4 Container
    • \\n
    \\n
  • \\n
  • \\n

    Valid values:

    \\n
      \\n
    • \\\"mp4\\\" (mp4 will also accept .mov and .m4v files)
    • \\n
    • \\\"wma\\\" (deprecated)
    • \\n
    • \\\"mp3\\\"
    • \\n
    • \\\"hls\\\"\\n-\\\"ism\\\" (smooth streaming)
    • \\n
    • \\\"dash\\\" (MPEG-DASH)
    • \\n
    • \\\"mkv\\\", \\\"mka\\\", \\\"mks\\\"
    • \\n
    \\n
  • \\n
  • \\n

    Deprecated:

    \\n
      \\n
    • \\\"wmv\\\"
    • \\n
    \\n
  • \\n
LengthFloatMovie Length in Seconds; Length zero displays at 0m, Length not set will not display3600 (one hour)
PlayStartFloatPlayStart defines the start position of the content, in seconds.

The player is not allowed to move to a position prior to this point. Any seek operation prior to this point will be clipped to PlayStart.

Channels can use PlayStart and PlayDuration to split one content piece into multiple clips and insert these clips with other content (typically advertisements) into one content list.

Starting from Roku OS 8.0, content metadata supports negative PlayStart values. This feature allows the media players to start playbacks distanced from the edge of the live stream
0
ClosedCaptionsBooleanBoolean indicating if CC icon should be displayedTrue
HDBrandedBooleanBoolean indicating if HD branding should be displayedTrue
IsHDBooleanBoolean indicating if content is HDTrue
SubtitleColorStringTheme metadata attribute that specifies the color to use when rendering subtitle text\\\"#FF0000\\\"
SubtitleConfigroAssociativeArray: {TrackName : String}Specifies the caption settings for content playback.

TrackName sets the name of the caption track to render. This string is a concatenation of the track source and track id, separated by a \\\"/\\\".

Valid track sources are: \\\"ism\\\", \\\"mkv\\\", \\\"eia608\\\" and \\\"dvb\\\".

The track id must match the track identifier in the smooth or mkvmanifest. For example, if an mkvfile has a caption track called “english1” the TrackName to select this track is “mkv/english1”.

When the track source is \\\"dvb\\\", the track id is the three-letter language code, with \\\"_sdh\\\" appended for subtitles for the deaf and hard of hearing. For example, \\\"dvb/eng_sdh\\\" are English subtitles for the deaf and hard of hearing and \\\"dvb/nor\\\" are normal Norwegian subtitles.

For sideloaded caption tracks, the TrackName is the url from where the caption track can be downloaded.Sideloaded caption formats can include srt,ttml, anddfxp. Specifying eia608/1 will trigger the Roku OS to search for embedded 608/708 captions in the stream. In the 8.0 Roku OS, automatic track selection based on a preferred caption language setting is introduced. Omit setting a URL here to avoid interfering with the automatic track selection. It is sufficient to add the URLs to SubtitleTracks
{ TrackName : \\\"mkv/english1\\\" }
SubtitleTracksroArray of roAssociativeArray: [{Language : String, Description : String, TrackName : String},...]SubtitleTracks sets the list of available caption tracks available to the stream. This list is added to the track list in the closed caption configuration dialog that is displayed during video playback when the user presses the Roku remote control * button. The captions from the selected track are then displayed on the screen. Language specifies the ISO 639.2B 3 character language code. This string is used to match the proper caption track with the audio language. Description specifies the text that will be shown for the corresponding track in the closed caption configuration dialog. For sideloaded caption tracks the TrackName is the URL from where the caption track can be downloaded. Sideloaded caption formats can include srt, ttml, and dfxp. The SubtitleTracks metadata is generally only used for side loaded captions. the Roku OS detects in-stream captions and thus specifying SubtitleTracks in this case is not necessary
SubtitleUrlStringSpecifies the path to an SRT or TTML formatted file used to render subtitles or closed captions, respectively. This is supported on roVideoScreen only. See Closed Caption Support for additional details\\\"mysite.com/vid/1932.srt\\\"; \\\"mysite.com/vid/1932.xml\\\"
VideoDisableUIBooleanIf set to true, hides the Scene Graph Video node trick play UI; If set to false (the default) shows the Scene Graph Video node trick play UIvideo = createObject(\\\"roSGNode\\\", \\\"Video\\\"); video.content.VideoDisableUI = true
EncodingTypeStringSpecifies the encoding scheme for PlayReady DRM, by setting to one of the following values:
    \\n
  • \\\"PlayReadyLicenseAcquisitionUrl\\\"
  • \\n
  • \\\"PlayReadyLicenseAcquisitionAndChallenge\\\" Note, this is the same value that used to be specified directly in Content Metadata structure
  • \\n
EncodingKeyStringSpecifies the PlayReady license acquisition URL, and additional custom request data, determined by the EncodingType attribute value specified:
    \\n
  • when encodingType=\\\"PlayReadyLicenseAcquisitionUrl\\\", the EncodingKey attribute contains the PlayReady license acquisition URL
  • \\n
SwitchingStrategyStringroVideoPlayer or roVideoScreen.

Specify different stream switching algorithms to be used in HLS adaptive streaming.
Only has an effect on HLS streams. \\\"full-adaptation\\\" uses measured bandwidth and buffer fullness to determine when to switch. This strategy requires that segments align across variants as the HLS spec requires. This is the new default
\\\"full-adaptation\\\"
WatchedBooleanFlag indicating if content has been watchedTrue
ForwardQueryStringParamsBooleanControls whether query string parameters from initial HLS stream manifest requests are forward to subsequent segment download requests. The default value is set to true for backwards compatibility.True
ForwardDashQueryStringParamsBooleanControls whether query string parameters from initial DASH stream manifest requests are forward to subsequent segment download requests. The default value is set to false for backwards compatibility.False
IgnoreStreamErrorsBooleanWhen set to true the media player will not stop playback when it runs into a streaming related error for this content. Instead, it will skip to the next item in the content list.

If this was the last item in the content list the media player will send a regular completion event (like isFullResult). Channels are still notified of any errors via an isRequestFailed notification but a new attribute in the event’s GetInfo object tells the channel the error was ignored.

See the changes related to isRequestFailed for more information. The default value is false.
video_details = {\\n    streamFormat: \\\"mp4\\\"\\n    ignoreStreamErrors: true\\n    streams: [{bitrate: 537, height: 360, width: 640, url: “https://...\\\"}]\\n}
AdaptiveMinStartBitrateIntegerMinimum startup bitrate specified in kbps. Streaming will start with a variant equal to or greater than this value. If this value is not set or if it's set to zero, any minimum start bitrate will be ignored.5000
AdaptiveMaxStartBitrateIntegerMaximum startup bitrate specified in kbps. Streaming will start with a variant less than or equal to this value. If this value is not set, it will default to 2500 kbps.2000
filterCodecProfilesBooleanFilters out any video profile/codec level combinations that the specified hardware cannot play. The default value is false, in which case no filtering occurs. Note that this currently only works for DASH streams.True
LiveBoundsPauseBehaviorStringAllows a channel to customize Media Player behavior on live streams when playing in the earliest part of a DVR buffer.

The stream remains paused even though it is playing in the earliest part of the buffer of a live stream when the value of the attribute is set to \\\"pause.\\\" This enables the Roku OS to distinguish between live streams and live streams that eventually transition to video on demand.

The possible values of this attribute are \\\"resume\\\", \\\"stop\\\", \\\"pause\\\", with resume being the default value.

Currently, this attribute will work only with Smooth and Dash streams. (Available since Roku OS 8.1)
Resume
ClipStartFloatClipStart sets the clip start position of the playback. The unit of ClipStart is seconds (Available since Roku OS 8.1).00.0
ClipEndFloatClipEnd sets the clip end position. The unit of ClipEnd is seconds (Available since Roku OS 8.1).00.0
PreferredAudioCodecStringSpecifies the audio codec that should be used during playback. The Media Player will select and report to the channel only those audio renditions that are encoded with the specified codec. Renditions that are encoded with a different codec are ignored. Possible values of this attribute are \\\"aac\\\", \\\"ac3\\\" and \\\"eac3\\\". (Available since Roku OS 9.0)\\\"aac\\\"
AudioWhitelistStringComma-separated list of audio tracks (based on ISO 639-1 or ISO 639-2 language code) that may be selected from the Audio track setting for the content.

\\\"en, spa\\\"
AudioBlacklistStringComma-separated list of audio tracks (based on ISO 639-1 or 639-2 language code) that may not be selected from the Audio track setting for the content.

(Available since Roku OS 9.4)

If a language is both blacklisted and whitelisted, the blacklisting takes precedence.
\\\"ita, fr\\\"
CaptionWhitelistStringComma-separated list of captioning tracks (based on ISO 639-2 language code) that may be selected from the Accessibility>Captioning track setting for the content.

\\\"en, spa\\\"
CaptionBlacklistStringComma-separated list of captioning tracks (based on ISO 639-2 language code) that may not be selected from the Accessibility>Captioning track setting for the content.

(Available since Roku OS 9.4)

If a language is both blacklisted and whitelisted, the blacklisting takes precedence.
\\\"deu, dan\\\"
\\n

CDN switching

\\n

Content Delivery Networks (CDNs) can be switched during playback to load balance traffic and failover to different servers in order to help optimize performance. The CdnConfig attribute can be used for managing load balancing and failovers.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesDescription
cdnConfigroArray of roAssociativeArrays\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
KeyRequired/ OptionalTypeDescription
URLFilterRequiredStringA substring that identifies the (base)URL to which these CDN settings apply.

The Roku media player matches this string against all (base)URLs listed in the manifest and applies the setting to all (base)URLs that contain this substring.
ContentFilterOptionalStringFor DASH streams, a substring that filters the period or asset ID to which these CDN settings apply.

The Roku player only applies these CDN setting to periods with a period ID or asset ID that contains this substring.

This match is used in addition to the URL filter.
PriorityRequiredIntegerFor configuring failovers, sets the priority for this (base)URL from 1 to x (a priority of 0 or less is invalid).

A lower value indicates a higher priority. For example, a (base)URL with a priority of 1 is higher than another with a priority of 10.

If the highest priority server fails, traffic is routed to the server with the next highest priority. If all servers are configured with the same priority, and one fails, no failover will happen.
WeightRequiredIntegerFor configuring load balancing, sets the relative weight for all (base)URLs with the same priority. This must be a value of 1 or greater (a weight of 0 disables a CDN).

The weight of a given BaseURL is its weight value divided by the sum of all weight values. This means that to spread the load equally across multiple CDNs with the same priority, set the weight for each to the same value. To configure the weights for two servers to 80% and a third server to 20%, for example, set servers one and two to 8 and server three to 4.
ServiceLocationOptionalStringA blacklist of failed BaseURL locations.
To use this field, create a child node and use a playlist (even though only one content item will be in the playlist). This field is updated only when contentIsPlayList is true.

The URLFilter, Priority, and Weight attributes must be specified to apply these configurations.
\\n

Example

\\n
this.cur_clip.CDNConfig = [\\n    {URLFilter:\\\"http://cdn1.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 1, weight: 50, serviceLocation: \\\"west\\\"},\\n    {URLFilter:\\\"http://cdn2.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 1, weight: 50, serviceLocation: \\\"east\\\"},\\n    {URLFilter:\\\"http://cdn1.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 2, weight: 50, serviceLocation: \\\"west\\\"},\\n    {URLFilter:\\\"http://cdn2.xyz.com/abc/\\\", ContentFilter, “testProgram”, priority: 2, weight: 50, serviceLocation: \\\"east\\\"},\\n]\\n
\\n

SceneGraph certificate attributes

\\n

The SceneGraph certificate meta-data attributes are used to configure\\nthe use of HTTP certificates and cookies by the Audio and Video nodes.\\nPlease note that when setting any of the following four attributes on\\na Video or Audio node, you need to be careful that the values are set on\\nthe correct HTTPAgent. If the node does not have its own HTTPAgent, set\\nby explicitly calling setHttpAgent() on the node, the Roku OS will\\ntraverse up the scene graph hierarchy until it finds the first node in\\nthe Video or Audio node's ancestry that has set an HTTPAgent. If none\\nis found, the values will be set on the global HTTPAgent which is always\\nguaranteed to exist. Therefore if you expect the header, etc. values\\nset to only apply to your Audio and Video nodes, create a unique\\ninstance of roHttpAgent for them and assign it directly. For example,\\nfor a Video node you should do the following:

\\n
'Assume video is a valid Video node instance\\n\\nhttpAgent = CreateObject(\\\"roHttpAgent\\\")\\nvideo.setHttpAgent(httpAgent)\\n
\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValues
HttpCertificatesFileuriIf set, the Scene Graph Audio or Video node loads this public certificate bundle, to authenticate the server. The protocol must be https for this to have any effect. When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When playing this content, the agent is updated in the following manner:
    \\n
  • If this attribute is defined, the file URI is set into the HttpAgent instance. However, if this attribute is specified and the value is the empty string (\\\"\\\"), then no changes will be made to the HttpAgent.
  • \\n
  • \\n

    If this attribute is not defined, the behavior depends upon whether the Content Meta-Data (CMD) contains secure (https) URLs:

    \\n
      \\n
    • If no secure URLs exist in the meta-data, then no certificates file path is set into the agent.
    • \\n
    • If a secure URL does exist, the platform's default certificates are set into the agent.
    • \\n
    \\n
  • \\n
HttpCookiesarray of stringsIf set, the Scene Graph Audio or Video node send the cookies to the server. Each cookie must have the following syntax: dom=domain;path=path;name=name;val=value; When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute is set, all HTTP cookies in the agent are cleared and replaced with the cookies defined by this attribute
HttpHeadersarray of stringsIf set, the Scene Graph Audio or Video node sends these headers to the server. Each string must be of the format \\\"name:value\\\". When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute is set, all HTTP headers in the agent are cleared and replaced with the headers defined by this attribute
HttpSendClientCertificateBooleanIf true, the Scene Graph Audio or Video node sends the client device certificate to the server, for client authentication. The protocol must be https for this to have any effect. When used with a Scene Graph Audio or Video node, the node or global HttpAgent is found, as explained elsewhere in this documentation. When this Content Meta-Data is played and this attribute exists, the value of this attribute (true or false) is set into the HttpAgent
\\n

drmHttpAgent for handling DRM key/license requests separately

\\n

Since Roku OS 9.3, you can create a separate agent to handle DRM key and license requests, apart from other types\\nof requests.

\\n

Once you have created your agent, you can set the Video node's drmHttpAgent field directly to designate that the special\\nagent is to supersede any currently-set agent in the case of DRM key and license requests. The drmHttpAgent field must be configured before setting the content in the Video node.

\\n
' Configure the DRM HttpAgent before setting content in the Video node\\n httpAgent = CreateObject(\\\"roHttpAgent\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-1\\\", \\\"weqweqweqweqweqweqeqeqeqeqwe\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-2\\\", \\\"fgfgfgfgfgfgfgfgfg\\\")\\n httpAgent.AddHeader(\\\"DRM-Specific-3\\\", \\\"zxzxzxzxxzxzxzxzxzx\\\")\\n m.video.drmHttpAgent = httpAgent    \\n m.video.content = videocontent\\n
\\n

If drmHttpAgent is not set (the default), uri fetches for video involving the DRM URLs\\n(serializationURL, licenseServerURL, licenseRenewURL) of ContentMetaData will\\nuse the video's regular HttpAgent. However, if the drmHttpAgent is set, the agent\\ncited in the field will be used for those fetches instead.

\\n
\\n

The \\\"SceneGraph Certificate Attributes\\\" mentioned above all have \\\"Drm\\\" versions,\\nwith names formed by the prefixing \\\"Drm\\\" to the \\\"regular\\\" names\\n(e.g., HttpCookies becomes DrmHttpCookies, and so forth).\\nThese attributes take precedence over those of the drmHttpAgent.

\\n
\\n

Playback control attributes

\\n

The playback control meta-data attributes are used to control\\nthe playback parameters for the content item.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
MinBandwidthIntegerroVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth higher than this minimum bandwidth. Units are kbps. By default Wowza servers set streams to 64 kbs, so you might want to set this parameter to something smaller than 64 when first testing Wowza streams. You will eventually want to specify the Wowza bitrates with a smil file (Please see the encoding guide)48
MaxBandwidthIntegerroVideoPlayer or roVideoScreen: Will only select variant streams with a bandwidth less than this maximum bandwidth. Units are kbps2500
AudioPIDPrefIntegerThis attribute is deprecated

Users can select their preferred audio language on-device in the Settings > Audio > Audio Preferred Language screen.
483
FullHDBooleanroVideoPlayer or roVideoScreen: Specify that this stream was encoded at 1080p resolutiontrue
FrameRateIntegerroVideoPlayer or roVideoScreen: Specify the 1080p stream was encoded at 24 or 30 fps24
\\n

Track ID attributes

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
TrackIDAudioStringroVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \\\"ISM\\\") to specify. Set the TrackIDAudio field to the desired track's StreamIndex.Name attribute from the manifest file\\\"Spanish\\\"
TrackIDVideoStringroVideoPlayer or roVideoScreen: Used in SmoothStreaming (StreamFormat = \\\"ISM\\\") to specify. Set the TrackIDVideo field to the desired track's StreamIndex.Name attribute from the manifest file\\\"h264video\\\"
TrackIDSubtitleStringroVideoPlayer: Used to specify a closed caption track in a video stream that supports 608/708 captions\\\"eia608/1\\\"
AudioFormatStringroSpringboardScreen: If set to \\\"dolby-digital\\\", will display a \\\"5.1 ))\\\" icon in the lower left of a movie style springboard screen\\\"dolby-digital\\\"
AudioLanguageSelectedStringThis attribute was deprecated as of the Roku 9.2 OS release.

Users can select their preferred audio language on-device in the Settings > Audio > Audio Preferred Language screen.
\\\"eng\\\"
\\n

roListScreen attributes

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
AttributeTypeValuesExample
SDBackgroundImageUrlStringroListScreen: URL for the SD background imagemysite.com/images/bg1_sd.jpg
HDBackgroundImageUrlStringroListScreen: URL for the HD background imagemysite.com/images/bg1_hd.jpg
\\n

Rating attribute icons

\\n

The Rating attribute contains the MPAA or TV rating stored as a string.\\nAt runtime, the ratings are shown with an icon instead of rendering the\\nstring as text. The following table shows the list of valid values for\\nthe Rating attribute, and the resulting icon that will be displayed for\\neach value.

\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n
ValueIcon
G\\\"roku815px
NC-17\\\"roku815px
PG\\\"roku815px
PG-13\\\"roku815px
R\\\"roku815px
UR\\\"roku815px
UNRATED\\\"roku815px
NR\\\"roku815px
TV-Y\\\"roku815px
TV-Y7\\\"roku815px
TV-Y7-FV\\\"roku815px
TV-G\\\"roku815px
TV-PG\\\"roku815px
TV-14\\\"roku815px
TV-MA\\\"roku815px
\\n

Content feed video lesson

\\n

You can learn how to link the content metadata in your channel's feed to a ContentNode by watching the Creating the content feed video lesson in Roku's SceneGraph: Build a Channel online video course.

\"\n}"} +{"https://developer.roku.com/api/v1/get-dev-cms-doc?filePath=left-nav%2Freferences.json&locale=en-us":"{\n \"content\": \"{\\n \\\"Reference overview\\\":\\\"/docs/references/references-overview.md\\\",\\n\\n \\\"SceneGraph\\\":{\\n \\\"Component functions\\\":{\\n \\\"init()\\\":\\\"/docs/references/scenegraph/component-functions/init.md\\\",\\n \\\"onKeyEvent()\\\":\\\"/docs/references/scenegraph/component-functions/onkeyevent.md\\\"\\n },\\n \\\"XML elements\\\":{\\n \\\"\\\":\\\"/docs/references/scenegraph/xml-elements/component.md\\\",\\n \\\"\\\":\\\"/docs/references/scenegraph/xml-elements/interface.md\\\",\\n \\\" `); - expect(parser.diagnostics).to.be.lengthOf(2); - expect(parser.diagnostics[0]).to.deep.include({ + expectDiagnostics(parser, [{ ...DiagnosticMessages.xmlUnexpectedChildren('field'), - range: Range.create(3, 9, 3, 14) - }); - expect(parser.diagnostics[1]).to.deep.include({ + location: { range: Range.create(3, 9, 3, 14) } + }, { ...DiagnosticMessages.xmlUnexpectedChildren('script'), - range: Range.create(7, 5, 7, 11) - }); + location: { range: Range.create(7, 5, 7, 11) } + }]); }); it('Adds error when whitespace appears before the prolog', () => { @@ -146,14 +143,12 @@ describe('SGParser', () => {