bitmovin-player-ui
Advanced tools
Comparing version 3.26.0 to 3.27.0
@@ -7,2 +7,10 @@ # Change Log | ||
## [3.27.0] | ||
### Added | ||
- Support for `CueUpdate` events that were introduced in player v8.60.0 | ||
### Fixed | ||
- Ad times (`remainingTime`, `playedTime` and `adDuration`) are not correctly rounded | ||
## [3.26.0] | ||
@@ -725,2 +733,3 @@ | ||
[3.27.0]: https://github.com/bitmovin/bitmovin-player-ui/compare/v3.26.0...v3.27.0 | ||
[3.26.0]: https://github.com/bitmovin/bitmovin-player-ui/compare/v3.25.0...v3.26.0 | ||
@@ -727,0 +736,0 @@ [3.25.0]: https://github.com/bitmovin/bitmovin-player-ui/compare/v3.24.0...v3.25.0 |
@@ -5,3 +5,3 @@ import { Container, ContainerConfig } from './container'; | ||
import { Size } from '../dom'; | ||
import { PlayerAPI } from 'bitmovin-player'; | ||
import { PlayerAPI, SubtitleCueEvent } from 'bitmovin-player'; | ||
import { VTTProperties } from 'bitmovin-player/types/subtitles/vtt/API'; | ||
@@ -25,2 +25,3 @@ /** | ||
configure(player: PlayerAPI, uimanager: UIInstanceManager): void; | ||
generateLabel(event: SubtitleCueEvent): SubtitleLabel; | ||
configureCea608Captions(player: PlayerAPI, uimanager: UIInstanceManager): void; | ||
@@ -48,2 +49,3 @@ enablePreviewSubtitleLabel(): void; | ||
constructor(subtitleOverlay: SubtitleOverlay); | ||
private getRegion; | ||
/** | ||
@@ -55,2 +57,3 @@ * Creates and wraps a subtitle label into a container div based on the subtitle region. | ||
addLabel(label: SubtitleLabel, overlaySize?: Size): void; | ||
replaceLabel(previousLabel: SubtitleLabel, newLabel: SubtitleLabel): void; | ||
/** | ||
@@ -57,0 +60,0 @@ * Removes a subtitle label from a container. |
@@ -47,10 +47,5 @@ "use strict"; | ||
player.on(player.exports.PlayerEvent.CueEnter, function (event) { | ||
// Sanitize cue data (must be done before the cue ID is generated in subtitleManager.cueEnter) | ||
if (event.position) { | ||
// Sometimes the positions are undefined, we assume them to be zero | ||
event.position.row = event.position.row || 0; | ||
event.position.column = event.position.column || 0; | ||
} | ||
var labelToAdd = subtitleManager.cueEnter(event); | ||
_this.preprocessLabelEventCallback.dispatch(event, labelToAdd); | ||
var label = _this.generateLabel(event); | ||
subtitleManager.cueEnter(event, label); | ||
_this.preprocessLabelEventCallback.dispatch(event, label); | ||
if (_this.previewSubtitleActive) { | ||
@@ -60,5 +55,13 @@ _this.subtitleContainerManager.removeLabel(_this.previewSubtitle); | ||
_this.show(); | ||
_this.subtitleContainerManager.addLabel(labelToAdd, _this.getDomElement().size()); | ||
_this.subtitleContainerManager.addLabel(label, _this.getDomElement().size()); | ||
_this.updateComponents(); | ||
}); | ||
player.on(player.exports.PlayerEvent.CueUpdate, function (event) { | ||
var label = _this.generateLabel(event); | ||
var labelToReplace = subtitleManager.cueUpdate(event, label); | ||
_this.preprocessLabelEventCallback.dispatch(event, label); | ||
if (labelToReplace) { | ||
_this.subtitleContainerManager.replaceLabel(labelToReplace, label); | ||
} | ||
}); | ||
player.on(player.exports.PlayerEvent.CueExit, function (event) { | ||
@@ -108,2 +111,19 @@ var labelToRemove = subtitleManager.cueExit(event); | ||
}; | ||
SubtitleOverlay.prototype.generateLabel = function (event) { | ||
// Sanitize cue data (must be done before the cue ID is generated in subtitleManager.cueEnter / update) | ||
if (event.position) { | ||
// Sometimes the positions are undefined, we assume them to be zero | ||
event.position.row = event.position.row || 0; | ||
event.position.column = event.position.column || 0; | ||
} | ||
var label = new SubtitleLabel({ | ||
// Prefer the HTML subtitle text if set, else try generating a image tag as string from the image attribute, | ||
// else use the plain text | ||
text: event.html || ActiveSubtitleManager.generateImageTagText(event.image) || event.text, | ||
vtt: event.vtt, | ||
region: event.region, | ||
regionStyle: event.regionStyle, | ||
}); | ||
return label; | ||
}; | ||
SubtitleOverlay.prototype.configureCea608Captions = function (player, uimanager) { | ||
@@ -227,4 +247,4 @@ var _this = this; | ||
SubtitleOverlay.prototype.enablePreviewSubtitleLabel = function () { | ||
this.previewSubtitleActive = true; | ||
if (!this.subtitleManager.hasCues) { | ||
this.previewSubtitleActive = true; | ||
this.subtitleContainerManager.addLabel(this.previewSubtitle); | ||
@@ -236,5 +256,7 @@ this.updateComponents(); | ||
SubtitleOverlay.prototype.removePreviewSubtitleLabel = function () { | ||
this.previewSubtitleActive = false; | ||
this.subtitleContainerManager.removeLabel(this.previewSubtitle); | ||
this.updateComponents(); | ||
if (this.previewSubtitleActive) { | ||
this.previewSubtitleActive = false; | ||
this.subtitleContainerManager.removeLabel(this.previewSubtitle); | ||
this.updateComponents(); | ||
} | ||
}; | ||
@@ -310,17 +332,15 @@ SubtitleOverlay.CLASS_CONTROLBAR_VISIBLE = 'controlbar-visible'; | ||
}; | ||
/** | ||
* Adds a subtitle cue to the manager and returns the label that should be added to the subtitle overlay. | ||
* @param event | ||
* @return {SubtitleLabel} | ||
*/ | ||
ActiveSubtitleManager.prototype.cueEnter = function (event) { | ||
ActiveSubtitleManager.prototype.cueEnter = function (event, label) { | ||
this.addCueToMap(event, label); | ||
}; | ||
ActiveSubtitleManager.prototype.cueUpdate = function (event, label) { | ||
var labelToReplace = this.popCueFromMap(event); | ||
if (labelToReplace) { | ||
this.addCueToMap(event, label); | ||
return labelToReplace; | ||
} | ||
return undefined; | ||
}; | ||
ActiveSubtitleManager.prototype.addCueToMap = function (event, label) { | ||
var id = ActiveSubtitleManager.calculateId(event); | ||
var label = new SubtitleLabel({ | ||
// Prefer the HTML subtitle text if set, else try generating a image tag as string from the image attribute, | ||
// else use the plain text | ||
text: event.html || ActiveSubtitleManager.generateImageTagText(event.image) || event.text, | ||
vtt: event.vtt, | ||
region: event.region, | ||
regionStyle: event.regionStyle, | ||
}); | ||
// Create array for id if it does not exist | ||
@@ -331,4 +351,20 @@ this.activeSubtitleCueMap[id] = this.activeSubtitleCueMap[id] || []; | ||
this.activeSubtitleCueCount++; | ||
return label; | ||
}; | ||
ActiveSubtitleManager.prototype.popCueFromMap = function (event) { | ||
var id = ActiveSubtitleManager.calculateId(event); | ||
var activeSubtitleCues = this.activeSubtitleCueMap[id]; | ||
if (activeSubtitleCues && activeSubtitleCues.length > 0) { | ||
// Remove cue | ||
/* We apply the FIFO approach here and remove the oldest cue from the associated id. When there are multiple cues | ||
* with the same id, there is no way to know which one of the cues is to be deleted, so we just hope that FIFO | ||
* works fine. Theoretically it can happen that two cues with colliding ids are removed at different times, in | ||
* the wrong order. This rare case has yet to be observed. If it ever gets an issue, we can take the unstable | ||
* cue end time (which can change between CueEnter and CueExit IN CueUpdate) and use it as an | ||
* additional hint to try and remove the correct one of the colliding cues. | ||
*/ | ||
var activeSubtitleCue = activeSubtitleCues.shift(); | ||
this.activeSubtitleCueCount--; | ||
return activeSubtitleCue.label; | ||
} | ||
}; | ||
ActiveSubtitleManager.generateImageTagText = function (imageData) { | ||
@@ -355,5 +391,2 @@ if (!imageData) { | ||
} | ||
else { | ||
return null; | ||
} | ||
}; | ||
@@ -367,20 +400,3 @@ /** | ||
ActiveSubtitleManager.prototype.cueExit = function (event) { | ||
var id = ActiveSubtitleManager.calculateId(event); | ||
var activeSubtitleCues = this.activeSubtitleCueMap[id]; | ||
if (activeSubtitleCues && activeSubtitleCues.length > 0) { | ||
// Remove cue | ||
/* We apply the FIFO approach here and remove the oldest cue from the associated id. When there are multiple cues | ||
* with the same id, there is no way to know which one of the cues is to be deleted, so we just hope that FIFO | ||
* works fine. Theoretically it can happen that two cues with colliding ids are removed at different times, in | ||
* the wrong order. This rare case has yet to be observed. If it ever gets an issue, we can take the unstable | ||
* cue end time (which can change between CueEnter and CueExit IN CueUpdate) and use it as an | ||
* additional hint to try and remove the correct one of the colliding cues. | ||
*/ | ||
var activeSubtitleCue = activeSubtitleCues.shift(); | ||
this.activeSubtitleCueCount--; | ||
return activeSubtitleCue.label; | ||
} | ||
else { | ||
return null; | ||
} | ||
return this.popCueFromMap(event); | ||
}; | ||
@@ -428,2 +444,14 @@ Object.defineProperty(ActiveSubtitleManager.prototype, "cueCount", { | ||
} | ||
SubtitleRegionContainerManager.prototype.getRegion = function (label) { | ||
if (label.vtt) { | ||
return { | ||
regionContainerId: label.vtt.region && label.vtt.region.id ? label.vtt.region.id : 'vtt', | ||
regionName: 'vtt', | ||
}; | ||
} | ||
return { | ||
regionContainerId: label.region || 'default', | ||
regionName: label.region || 'default', | ||
}; | ||
}; | ||
/** | ||
@@ -435,11 +463,3 @@ * Creates and wraps a subtitle label into a container div based on the subtitle region. | ||
SubtitleRegionContainerManager.prototype.addLabel = function (label, overlaySize) { | ||
var regionContainerId; | ||
var regionName; | ||
if (label.vtt) { | ||
regionContainerId = label.vtt.region && label.vtt.region.id ? label.vtt.region.id : 'vtt'; | ||
regionName = 'vtt'; | ||
} | ||
else { | ||
regionContainerId = regionName = label.region || 'default'; | ||
} | ||
var _a = this.getRegion(label), regionContainerId = _a.regionContainerId, regionName = _a.regionName; | ||
var cssClasses = ["subtitle-position-" + regionName]; | ||
@@ -474,2 +494,7 @@ if (label.vtt && label.vtt.region) { | ||
}; | ||
SubtitleRegionContainerManager.prototype.replaceLabel = function (previousLabel, newLabel) { | ||
var regionContainerId = this.getRegion(previousLabel).regionContainerId; | ||
this.subtitleRegionContainers[regionContainerId].removeLabel(previousLabel); | ||
this.subtitleRegionContainers[regionContainerId].addLabel(newLabel); | ||
}; | ||
/** | ||
@@ -476,0 +501,0 @@ * Removes a subtitle label from a container. |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.version = void 0; | ||
exports.version = '3.26.0'; | ||
exports.version = '3.27.0'; | ||
// Management | ||
@@ -6,0 +6,0 @@ var uimanager_1 = require("./uimanager"); |
@@ -109,3 +109,3 @@ "use strict"; | ||
} | ||
return formatNumber(time, formatString); | ||
return formatNumber(Math.round(time), formatString); | ||
}); | ||
@@ -112,0 +112,0 @@ } |
{ | ||
"name": "bitmovin-player-ui", | ||
"version": "3.26.0", | ||
"version": "3.27.0", | ||
"description": "Bitmovin Player UI Framework", | ||
@@ -5,0 +5,0 @@ "main": "./dist/js/framework/main.js", |
@@ -35,3 +35,3 @@ import { MockHelper, TestingPlayerAPI } from '../helper/MockHelper'; | ||
it('removes a subtitle label con cueExit', () => { | ||
it('removes a subtitle label on cueExit', () => { | ||
playerMock.eventEmitter.fireSubtitleCueEnterEvent(); | ||
@@ -43,3 +43,25 @@ const removeLabelSpy = jest.spyOn(subtitleRegionContainerManagerMock, 'removeLabel'); | ||
}); | ||
it('updates a subtitle label on cueUpdate', () => { | ||
const updateLabelSpy = jest.spyOn(subtitleRegionContainerManagerMock, 'replaceLabel'); | ||
jest.spyOn(subtitleOverlay, 'getDomElement').mockReturnValue(mockDomElement); | ||
playerMock.eventEmitter.fireSubtitleCueEnterEvent(); | ||
expect(updateLabelSpy).not.toHaveBeenCalled(); | ||
playerMock.eventEmitter.fireSubtitleCueUpdateEvent(); | ||
expect(updateLabelSpy).toHaveBeenCalled(); | ||
}); | ||
it('ignores cueUpdate event if it does not match a previous cue', () => { | ||
const updateLabelSpy = jest.spyOn(subtitleRegionContainerManagerMock, 'replaceLabel'); | ||
jest.spyOn(subtitleOverlay, 'getDomElement').mockReturnValue(mockDomElement); | ||
playerMock.eventEmitter.fireSubtitleCueEnterEvent(); | ||
expect(updateLabelSpy).not.toHaveBeenCalled(); | ||
playerMock.eventEmitter.fireSubtitleCueUpdateEvent('some different text'); | ||
expect(updateLabelSpy).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
@@ -309,2 +309,12 @@ import { | ||
fireSubtitleCueUpdateEvent(text = 'Test Subtitle'): void { | ||
this.fireEvent<SubtitleCueEvent>({ | ||
subtitleId: 'subtitleId', | ||
start: 0, | ||
end: 10, | ||
text, | ||
type: PlayerEvent.CueUpdate, | ||
} as SubtitleCueEvent); | ||
} | ||
fireSubtitleCueExitEvent(): void { | ||
@@ -311,0 +321,0 @@ this.fireEvent<SubtitleCueEvent>({ |
@@ -57,13 +57,7 @@ import { Container, ContainerConfig } from './container'; | ||
player.on(player.exports.PlayerEvent.CueEnter, (event: SubtitleCueEvent) => { | ||
// Sanitize cue data (must be done before the cue ID is generated in subtitleManager.cueEnter) | ||
if (event.position) { | ||
// Sometimes the positions are undefined, we assume them to be zero | ||
event.position.row = event.position.row || 0; | ||
event.position.column = event.position.column || 0; | ||
} | ||
const label = this.generateLabel(event); | ||
subtitleManager.cueEnter(event, label); | ||
let labelToAdd = subtitleManager.cueEnter(event); | ||
this.preprocessLabelEventCallback.dispatch(event, label); | ||
this.preprocessLabelEventCallback.dispatch(event, labelToAdd); | ||
if (this.previewSubtitleActive) { | ||
@@ -75,6 +69,17 @@ this.subtitleContainerManager.removeLabel(this.previewSubtitle); | ||
this.subtitleContainerManager.addLabel(labelToAdd, this.getDomElement().size()); | ||
this.subtitleContainerManager.addLabel(label, this.getDomElement().size()); | ||
this.updateComponents(); | ||
}); | ||
player.on(player.exports.PlayerEvent.CueUpdate, (event: SubtitleCueEvent) => { | ||
const label = this.generateLabel(event); | ||
const labelToReplace = subtitleManager.cueUpdate(event, label); | ||
this.preprocessLabelEventCallback.dispatch(event, label); | ||
if (labelToReplace) { | ||
this.subtitleContainerManager.replaceLabel(labelToReplace, label); | ||
} | ||
}); | ||
player.on(player.exports.PlayerEvent.CueExit, (event: SubtitleCueEvent) => { | ||
@@ -130,2 +135,22 @@ let labelToRemove = subtitleManager.cueExit(event); | ||
generateLabel(event: SubtitleCueEvent): SubtitleLabel { | ||
// Sanitize cue data (must be done before the cue ID is generated in subtitleManager.cueEnter / update) | ||
if (event.position) { | ||
// Sometimes the positions are undefined, we assume them to be zero | ||
event.position.row = event.position.row || 0; | ||
event.position.column = event.position.column || 0; | ||
} | ||
const label = new SubtitleLabel({ | ||
// Prefer the HTML subtitle text if set, else try generating a image tag as string from the image attribute, | ||
// else use the plain text | ||
text: event.html || ActiveSubtitleManager.generateImageTagText(event.image) || event.text, | ||
vtt: event.vtt, | ||
region: event.region, | ||
regionStyle: event.regionStyle, | ||
}); | ||
return label; | ||
} | ||
configureCea608Captions(player: PlayerAPI, uimanager: UIInstanceManager): void { | ||
@@ -262,4 +287,4 @@ // The calculated font size | ||
enablePreviewSubtitleLabel(): void { | ||
this.previewSubtitleActive = true; | ||
if (!this.subtitleManager.hasCues) { | ||
this.previewSubtitleActive = true; | ||
this.subtitleContainerManager.addLabel(this.previewSubtitle); | ||
@@ -272,5 +297,7 @@ this.updateComponents(); | ||
removePreviewSubtitleLabel(): void { | ||
this.previewSubtitleActive = false; | ||
this.subtitleContainerManager.removeLabel(this.previewSubtitle); | ||
this.updateComponents(); | ||
if (this.previewSubtitleActive) { | ||
this.previewSubtitleActive = false; | ||
this.subtitleContainerManager.removeLabel(this.previewSubtitle); | ||
this.updateComponents(); | ||
} | ||
} | ||
@@ -347,19 +374,20 @@ } | ||
/** | ||
* Adds a subtitle cue to the manager and returns the label that should be added to the subtitle overlay. | ||
* @param event | ||
* @return {SubtitleLabel} | ||
*/ | ||
cueEnter(event: SubtitleCueEvent): SubtitleLabel { | ||
cueEnter(event: SubtitleCueEvent, label: SubtitleLabel): void { | ||
this.addCueToMap(event, label); | ||
} | ||
cueUpdate(event: SubtitleCueEvent, label: SubtitleLabel): SubtitleLabel | undefined { | ||
const labelToReplace = this.popCueFromMap(event); | ||
if (labelToReplace) { | ||
this.addCueToMap(event, label); | ||
return labelToReplace; | ||
} | ||
return undefined; | ||
} | ||
private addCueToMap(event: SubtitleCueEvent, label: SubtitleLabel): void { | ||
let id = ActiveSubtitleManager.calculateId(event); | ||
let label = new SubtitleLabel({ | ||
// Prefer the HTML subtitle text if set, else try generating a image tag as string from the image attribute, | ||
// else use the plain text | ||
text: event.html || ActiveSubtitleManager.generateImageTagText(event.image) || event.text, | ||
vtt: event.vtt, | ||
region: event.region, | ||
regionStyle: event.regionStyle, | ||
}); | ||
// Create array for id if it does not exist | ||
@@ -371,7 +399,25 @@ this.activeSubtitleCueMap[id] = this.activeSubtitleCueMap[id] || []; | ||
this.activeSubtitleCueCount++; | ||
} | ||
return label; | ||
private popCueFromMap(event: SubtitleCueEvent): SubtitleLabel | undefined { | ||
let id = ActiveSubtitleManager.calculateId(event); | ||
let activeSubtitleCues = this.activeSubtitleCueMap[id]; | ||
if (activeSubtitleCues && activeSubtitleCues.length > 0) { | ||
// Remove cue | ||
/* We apply the FIFO approach here and remove the oldest cue from the associated id. When there are multiple cues | ||
* with the same id, there is no way to know which one of the cues is to be deleted, so we just hope that FIFO | ||
* works fine. Theoretically it can happen that two cues with colliding ids are removed at different times, in | ||
* the wrong order. This rare case has yet to be observed. If it ever gets an issue, we can take the unstable | ||
* cue end time (which can change between CueEnter and CueExit IN CueUpdate) and use it as an | ||
* additional hint to try and remove the correct one of the colliding cues. | ||
*/ | ||
let activeSubtitleCue = activeSubtitleCues.shift(); | ||
this.activeSubtitleCueCount--; | ||
return activeSubtitleCue.label; | ||
} | ||
} | ||
private static generateImageTagText(imageData: string): string { | ||
static generateImageTagText(imageData: string): string | undefined { | ||
if (!imageData) { | ||
@@ -393,3 +439,3 @@ return; | ||
*/ | ||
getCues(event: SubtitleCueEvent): SubtitleLabel[] { | ||
getCues(event: SubtitleCueEvent): SubtitleLabel[] | undefined { | ||
let id = ActiveSubtitleManager.calculateId(event); | ||
@@ -399,4 +445,2 @@ let activeSubtitleCues = this.activeSubtitleCueMap[id]; | ||
return activeSubtitleCues.map((cue) => cue.label); | ||
} else { | ||
return null; | ||
} | ||
@@ -412,21 +456,3 @@ } | ||
cueExit(event: SubtitleCueEvent): SubtitleLabel { | ||
let id = ActiveSubtitleManager.calculateId(event); | ||
let activeSubtitleCues = this.activeSubtitleCueMap[id]; | ||
if (activeSubtitleCues && activeSubtitleCues.length > 0) { | ||
// Remove cue | ||
/* We apply the FIFO approach here and remove the oldest cue from the associated id. When there are multiple cues | ||
* with the same id, there is no way to know which one of the cues is to be deleted, so we just hope that FIFO | ||
* works fine. Theoretically it can happen that two cues with colliding ids are removed at different times, in | ||
* the wrong order. This rare case has yet to be observed. If it ever gets an issue, we can take the unstable | ||
* cue end time (which can change between CueEnter and CueExit IN CueUpdate) and use it as an | ||
* additional hint to try and remove the correct one of the colliding cues. | ||
*/ | ||
let activeSubtitleCue = activeSubtitleCues.shift(); | ||
this.activeSubtitleCueCount--; | ||
return activeSubtitleCue.label; | ||
} else { | ||
return null; | ||
} | ||
return this.popCueFromMap(event); | ||
} | ||
@@ -470,2 +496,16 @@ | ||
private getRegion(label: SubtitleLabel): { regionContainerId: string, regionName: string } { | ||
if (label.vtt) { | ||
return { | ||
regionContainerId: label.vtt.region && label.vtt.region.id ? label.vtt.region.id : 'vtt', | ||
regionName: 'vtt', | ||
}; | ||
} | ||
return { | ||
regionContainerId: label.region || 'default', | ||
regionName: label.region || 'default', | ||
}; | ||
} | ||
/** | ||
@@ -477,12 +517,3 @@ * Creates and wraps a subtitle label into a container div based on the subtitle region. | ||
addLabel(label: SubtitleLabel, overlaySize?: Size): void { | ||
let regionContainerId; | ||
let regionName; | ||
if (label.vtt) { | ||
regionContainerId = label.vtt.region && label.vtt.region.id ? label.vtt.region.id : 'vtt'; | ||
regionName = 'vtt'; | ||
} else { | ||
regionContainerId = regionName = label.region || 'default'; | ||
} | ||
const { regionContainerId, regionName } = this.getRegion(label); | ||
const cssClasses = [`subtitle-position-${regionName}`]; | ||
@@ -522,2 +553,9 @@ | ||
replaceLabel(previousLabel: SubtitleLabel, newLabel: SubtitleLabel): void { | ||
const { regionContainerId } = this.getRegion(previousLabel); | ||
this.subtitleRegionContainers[regionContainerId].removeLabel(previousLabel); | ||
this.subtitleRegionContainers[regionContainerId].addLabel(newLabel); | ||
} | ||
/** | ||
@@ -524,0 +562,0 @@ * Removes a subtitle label from a container. |
@@ -116,3 +116,3 @@ import { PlayerAPI } from 'bitmovin-player'; | ||
} | ||
return formatNumber(time, formatString); | ||
return formatNumber(Math.round(time), formatString); | ||
}); | ||
@@ -119,0 +119,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4403682
49201