UNPKG

@l5i/dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

321 lines (279 loc) 14.4 kB
/** * The copyright in this software is being made available under the BSD License, * included below. This software may be subject to other third party and contributor * rights, including patent rights, and no such rights are granted under this license. * * Copyright (c) 2013, Dash Industry Forum. * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * Neither the name of Dash Industry Forum nor the names of its * contributors may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ import FactoryMaker from '../../core/FactoryMaker'; function EmbeddedTextHtmlRender() { let captionId = 0; let instance; /* HTML Rendering functions */ function checkIndent(chars) { let line = ''; for (let c = 0; c < chars.length; ++c) { const uc = chars[c]; line += uc.uchar; } const l = line.length; const ll = line.replace(/^\s+/,'').length; return l - ll; } function getRegionProperties(region) { return 'left: ' + (region.x * 3.125) + '%; top: ' + (region.y1 * 6.66) + '%; width: ' + (100 - (region.x * 3.125)) + '%; height: ' + (Math.max((region.y2 - 1) - region.y1, 1) * 6.66) + '%; align-items: flex-start; overflow: visible; -webkit-writing-mode: horizontal-tb;'; } function createRGB(color) { if (color === 'red') { return 'rgb(255, 0, 0)'; } else if (color === 'green') { return 'rgb(0, 255, 0)'; } else if (color === 'blue') { return 'rgb(0, 0, 255)'; } else if (color === 'cyan') { return 'rgb(0, 255, 255)'; } else if (color === 'magenta') { return 'rgb(255, 0, 255)'; } else if (color === 'yellow') { return 'rgb(255, 255, 0)'; } else if (color === 'white') { return 'rgb(255, 255, 255)'; } else if (color === 'black') { return 'rgb(0, 0, 0)'; } return color; } function getStyle(videoElement, style) { const fontSize = videoElement.videoHeight / 15.0; if (style) { return 'font-size: ' + fontSize + 'px; font-family: Menlo, Consolas, \'Cutive Mono\', monospace; color: ' + ((style.foreground) ? createRGB(style.foreground) : 'rgb(255, 255, 255)') + '; font-style: ' + (style.italics ? 'italic' : 'normal') + '; text-decoration: ' + (style.underline ? 'underline' : 'none') + '; white-space: pre; background-color: ' + ((style.background) ? createRGB(style.background) : 'transparent') + ';'; } else { return 'font-size: ' + fontSize + 'px; font-family: Menlo, Consolas, \'Cutive Mono\', monospace; justify-content: flex-start; text-align: left; color: rgb(255, 255, 255); font-style: normal; white-space: pre; line-height: normal; font-weight: normal; text-decoration: none; width: 100%; display: flex;'; } } function ltrim(s) { return s.replace(/^\s+/g, ''); } function rtrim(s) { return s.replace(/\s+$/g, ''); } function createHTMLCaptionsFromScreen(videoElement, startTime, endTime, captionScreen) { let currRegion = null; let existingRegion = null; let lastRowHasText = false; let lastRowIndentL = -1; let currP = {start: startTime, end: endTime, spans: []}; let currentStyle = 'style_cea608_white_black'; const seenRegions = {}; const styleStates = {}; const regions = []; let r, s; for (r = 0; r < 15; ++r) { const row = captionScreen.rows[r]; let line = ''; let prevPenState = null; if (false === row.isEmpty()) { /* Row is not empty */ /* Get indentation of this row */ const rowIndent = checkIndent(row.chars); /* Create a new region is there is none */ if (currRegion === null) { currRegion = { x: rowIndent, y1: r, y2: (r + 1), p: [] }; } /* Check if indentation has changed and we had text of last row */ if ((rowIndent !== lastRowIndentL) && lastRowHasText) { currRegion.p.push(currP); currP = { start: startTime, end: endTime, spans: [] }; currRegion.y2 = r; currRegion.name = 'region_' + currRegion.x + '_' + currRegion.y1 + '_' + currRegion.y2; if (false === seenRegions.hasOwnProperty(currRegion.name)) { regions.push(currRegion); seenRegions[currRegion.name] = currRegion; } else { existingRegion = seenRegions[currRegion.name]; existingRegion.p.contat(currRegion.p); } currRegion = { x: rowIndent, y1: r, y2: (r + 1), p: [] }; } for (let c = 0; c < row.chars.length; ++c) { const uc = row.chars[c]; const currPenState = uc.penState; if ((prevPenState === null) || (!currPenState.equals(prevPenState))) { if (line.trim().length > 0) { currP.spans.push({ name: currentStyle, line: line, row: r }); line = ''; } let currPenStateString = 'style_cea608_' + currPenState.foreground + '_' + currPenState.background; if (currPenState.underline) { currPenStateString += '_underline'; } if (currPenState.italics) { currPenStateString += '_italics'; } if (!styleStates.hasOwnProperty(currPenStateString)) { styleStates[currPenStateString] = JSON.parse(JSON.stringify(currPenState)); } prevPenState = currPenState; currentStyle = currPenStateString; } line += uc.uchar; } if (line.trim().length > 0) { currP.spans.push({ name: currentStyle, line: line, row: r }); } lastRowHasText = true; lastRowIndentL = rowIndent; } else { /* Row is empty */ lastRowHasText = false; lastRowIndentL = -1; if (currRegion) { currRegion.p.push(currP); currP = { start: startTime, end: endTime, spans: [] }; currRegion.y2 = r; currRegion.name = 'region_' + currRegion.x + '_' + currRegion.y1 + '_' + currRegion.y2; if (false === seenRegions.hasOwnProperty(currRegion.name)) { regions.push(currRegion); seenRegions[currRegion.name] = currRegion; } else { existingRegion = seenRegions[currRegion.name]; existingRegion.p.contat(currRegion.p); } currRegion = null; } } } if (currRegion) { currRegion.p.push(currP); currRegion.y2 = r + 1; currRegion.name = 'region_' + currRegion.x + '_' + currRegion.y1 + '_' + currRegion.y2; if (false === seenRegions.hasOwnProperty(currRegion.name)) { regions.push(currRegion); seenRegions[currRegion.name] = currRegion; } else { existingRegion = seenRegions[currRegion.name]; existingRegion.p.contat(currRegion.p); } currRegion = null; } const captionsArray = []; /* Loop thru regions */ for (r = 0; r < regions.length; ++r) { const region = regions[r]; const cueID = 'sub_cea608_' + (captionId++); const finalDiv = document.createElement('div'); finalDiv.id = cueID; const cueRegionProperties = getRegionProperties(region); finalDiv.style.cssText = 'position: absolute; margin: 0; display: flex; box-sizing: border-box; pointer-events: none;' + cueRegionProperties; const bodyDiv = document.createElement('div'); bodyDiv.className = 'paragraph bodyStyle'; bodyDiv.style.cssText = getStyle(videoElement); const cueUniWrapper = document.createElement('div'); cueUniWrapper.className = 'cueUniWrapper'; cueUniWrapper.style.cssText = 'unicode-bidi: normal; direction: ltr;'; for (let p = 0; p < region.p.length; ++p) { const ptag = region.p[p]; let lastSpanRow = 0; for (s = 0; s < ptag.spans.length; ++s) { let span = ptag.spans[s]; if (span.line.length > 0) { if ((s !== 0) && lastSpanRow != span.row) { const brElement = document.createElement('br'); brElement.className = 'lineBreak'; cueUniWrapper.appendChild(brElement); } let sameRow = false; if (lastSpanRow === span.row) { sameRow = true; } lastSpanRow = span.row; const spanStyle = styleStates[span.name]; const spanElement = document.createElement('span'); spanElement.className = 'spanPadding ' + span.name + ' customSpanColor'; spanElement.style.cssText = getStyle(videoElement, spanStyle); /* If this is not the first span, and it's on the same * row as the last one */ if ((s !== 0) && sameRow) { /* and it's the last span on this row */ if (s === ptag.spans.length - 1) { /* trim only the right side */ spanElement.textContent = rtrim(span.line); } else { /* don't trim at all */ spanElement.textContent = span.line; } } else { /* if there is more than 1 span and this isn't the last span */ if (ptag.spans.length > 1 && s < (ptag.spans.length - 1)) { /* Check if next text is on same row */ if (span.row === ptag.spans[s + 1].row) { /* Next element on same row, trim start */ spanElement.textContent = ltrim(span.line); } else { /* Different rows, trim both */ spanElement.textContent = span.line.trim(); } } else { spanElement.textContent = span.line.trim(); } } cueUniWrapper.appendChild(spanElement); } } } bodyDiv.appendChild(cueUniWrapper); finalDiv.appendChild(bodyDiv); const fontSize = { 'bodyStyle': ['%', 90] }; for (const s in styleStates) { if (styleStates.hasOwnProperty(s)) { fontSize[s] = ['%', 90]; } } captionsArray.push({ type: 'html', start: startTime, end: endTime, cueHTMLElement: finalDiv, cueID: cueID, cellResolution: [32, 15], isFromCEA608: true, regions: regions, regionID: region.name, videoHeight: videoElement.videoHeight, videoWidth: videoElement.videoWidth, fontSize: fontSize, lineHeight: {}, linePadding: {} }); } return captionsArray; } instance = { createHTMLCaptionsFromScreen: createHTMLCaptionsFromScreen }; return instance; } EmbeddedTextHtmlRender.__dashjs_factory_name = 'EmbeddedTextHtmlRender'; export default FactoryMaker.getSingletonFactory(EmbeddedTextHtmlRender);