H264Session.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. function H264Session() {
  2. let rtpTimeStamp = 0;
  3. let size1M = 1048576; //1024 * 1024
  4. let inputBuffer = new Uint8Array(size1M);
  5. let spsSegment = null;
  6. let ppsSegment = null;
  7. let SPSParser = null;
  8. let width = 0;
  9. let height = 0;
  10. let inputLength = 0;
  11. let initalSegmentFlag = true; //用于确定是否是initSegment
  12. let initalMediaFrameFlag = true;
  13. let frameRate = null; //根据SDP或者SPS设置
  14. let preSample = null; //上一个Sample
  15. let inputSegBufferSub = null;
  16. //MSE使用的数据以及相关配置,顺序codecInfo -> initSegmentData -> mediaSample -> frameData
  17. //时间戳用于绘制人脸框
  18. let decodedData = {
  19. frameData: null, //视频数据
  20. timeStamp: null, //时间戳
  21. initSegmentData: null, //MP4配置,用于initsegment
  22. mediaSample: null, //使用duration控制每一帧的播放时间
  23. codecInfo: "", //MSE init时传入,用于创建mediasource
  24. };
  25. let FRAMETYPE = {
  26. 1: 'P',
  27. 5: 'I',
  28. 6: 'SEI',
  29. 7: 'I'
  30. };
  31. let frameType = '';
  32. let decodeMode = 'canvas';
  33. let outputSize = 0;
  34. let curSize = 0;
  35. const PREFIX = new Uint8Array(['0x00', '0x00', '0x00', '0x01']);
  36. let firstIframe = false;
  37. let SEIInfo = {
  38. ivs: null,
  39. timestamp:null,
  40. };
  41. let preWidth = null,
  42. preHeight = null;
  43. let resetTimeCount = 0;
  44. let lastTimeStamp = 0;
  45. //const RESETTIME = 162000000;
  46. const RESETTIME = 4320000;
  47. let SEIBuffer = null;
  48. let lastTime =0;
  49. let decoder = null;
  50. function constructor() {
  51. }
  52. constructor.prototype = {
  53. init() {
  54. SPSParser = new H264SPSParser();
  55. decoder = new H264Decoder();
  56. this.resolutionChangedCallback = ()=>{};
  57. },
  58. remuxRTPData(rtspInterleaved, rtpHeader, rtpPayload) {
  59. //console.log(rtspInterleaved)
  60. //console.log(rtpHeader)
  61. let PaddingSize = 0;
  62. let extensionHeaderLen = 0; //如果RtpHeader.X=1,则在RTP报头后跟有一个扩展报头
  63. let PAYLOAD = null;
  64. //console.log(rtpHeader)
  65. //console.log(rtspInterleaved, rtpHeader, rtpPayload.subarray(0,5))
  66. let RtpHeader = {
  67. V: rtpHeader[0] >>> 6,
  68. P: rtpHeader[0] & 0x20,
  69. X: rtpHeader[0] & 0x10,
  70. CC: rtpHeader[0] & 0x0F,
  71. M: (rtpHeader[1] & 0x80) >> 7,
  72. PT: rtpHeader[1] & 127,
  73. SN: (rtpHeader[2] << 8) + rtpHeader[3],
  74. timeStamp: (rtpHeader[4] << 24) + (rtpHeader[5] << 16) + (rtpHeader[6] << 8) + rtpHeader[7],
  75. SSRC: (rtpHeader[8] << 24) + (rtpHeader[9] << 16) + (rtpHeader[10] << 8) + rtpHeader[11],
  76. };
  77. if (RtpHeader.P) { //填充
  78. PaddingSize = rtpPayload[rtpPayload.length - 1];
  79. //console.log("Padding - " + PaddingSize);
  80. }
  81. if (RtpHeader.X) { //扩展
  82. extensionHeaderLen = (((rtpPayload[2] << 8) | rtpPayload[3]) * 4) + 4;
  83. console.log('X: ' + rtpPayload[0])
  84. }
  85. //console.log('extensionHeaderLen: '+ extensionHeaderLen)
  86. PAYLOAD = rtpPayload.subarray(extensionHeaderLen, rtpPayload.length - PaddingSize);
  87. rtpTimeStamp = RtpHeader.timeStamp;
  88. //console.log(rtpTimeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6] , rtpHeader[7], PAYLOAD[0] & 0x1f)
  89. /* 载荷结构(https://blog.csdn.net/davebobo/article/details/52994596)
  90. +---------------+
  91. |0|1|2|3|4|5|6|7|
  92. +-+-+-+-+-+-+-+-+
  93. |F|NRI| Type |
  94. +---------------+
  95. Type = 1-23 单个NAL单元包
  96. Type = 24,25, 26, 27聚合包
  97. Type = 28,29, 分片单元
  98. */
  99. //console.log(rtspInterleaved,rtpHeader, PAYLOAD[0]);
  100. let nalType = (PAYLOAD[0] & 0x1f);
  101. //console.log(PAYLOAD[0] + ' nalType: ' + nalType);
  102. //console.log('rtpPayload.length: ' + rtpPayload.length)
  103. //console.log(nalType, PAYLOAD[0])
  104. //console.log('nalType: ' + nalType, RtpHeader.M, rtpTimeStamp)
  105. //console.log('nalType: ', nalType, ' timestamp: ', rtpTimeStamp);
  106. switch (nalType) {
  107. case 6: //SEI
  108. //console.log(PAYLOAD, String.fromCharCode.apply(null, PAYLOAD))
  109. if(SEIParse(PAYLOAD) === null) {
  110. return;
  111. }
  112. inputBuffer = setBuffer(inputBuffer, PREFIX);
  113. inputBuffer = setBuffer(inputBuffer, PAYLOAD);
  114. break;
  115. case 7: //SPS
  116. //console.log('SPS');
  117. SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD));
  118. let sizeInfo = SPSParser.getSizeInfo();
  119. //console.log(SPSParser.getSpsMap())
  120. width = sizeInfo.width;
  121. height = sizeInfo.height;
  122. if(preWidth !== width || preHeight !== height) {
  123. console.log('resolution changed!');
  124. console.log('preWidth: ', preWidth, ' preHeight: ', preHeight, ' width: ', width, ' height: ', height);
  125. preWidth = width;
  126. preHeight = height;
  127. }
  128. inputBuffer = setBuffer(inputBuffer, PREFIX);
  129. inputBuffer = setBuffer(inputBuffer, PAYLOAD);
  130. spsSegment = PAYLOAD;
  131. //console.log('width: ',width, 'height: ', height)
  132. curSize = sizeInfo.decodeSize;
  133. firstIframe = true;
  134. //console.log(spsSegment)
  135. if (frameRate === null) {
  136. frameRate = SPSParser.getFPS();
  137. }
  138. break;
  139. case 8: //PPS
  140. //console.log('PPS')
  141. inputBuffer = setBuffer(inputBuffer, PREFIX);
  142. inputBuffer = setBuffer(inputBuffer, PAYLOAD);
  143. ppsSegment = PAYLOAD;
  144. //console.log(ppsSegment)
  145. break;
  146. case 28: //FU
  147. //console.log('FU');
  148. let startBit = ((PAYLOAD[1] & 0x80) === 0x80),
  149. endBit = ((PAYLOAD[1] & 0x40) === 0x40),
  150. fuType = PAYLOAD[1] & 0x1f,
  151. payloadStartIndex = 2;
  152. //console.log('startBit: ' + startBit + ' endBit: ' + endBit)
  153. //console.log('fuType: ' + fuType)
  154. if (startBit === true && endBit === false) {
  155. let newNalHeader = new Uint8Array(1);
  156. newNalHeader[0] = ((PAYLOAD[0] & 0xe0) | fuType);
  157. //console.log('newNalHeader: ', newNalHeader[0])
  158. //console.log('fuType: ' + fuType)
  159. //console.log((PAYLOAD[2] << 8) + PAYLOAD[3])
  160. //console.log(new Uint8Array(PAYLOAD.subarray(0, 100)));
  161. if (false) { //赛兰摄像头,SPS,PPS,I帧打在一个RTP包中
  162. PAYLOAD[1] = newNalHeader[0];
  163. SPSParser.parse(removeH264or5EmulationBytes(PAYLOAD.subarray(1, 27)));
  164. let sizeInfo = SPSParser.getSizeInfo();
  165. //console.log(sizeInfo, SPSParser.getSpsMap())
  166. //SPS
  167. width = sizeInfo.width;
  168. height = sizeInfo.height;
  169. inputBuffer = setBuffer(inputBuffer, PREFIX);
  170. inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(1, 27));
  171. spsSegment = PAYLOAD.subarray(1, 27);
  172. //console.log('width: ',width, 'height: ', height)
  173. curSize = sizeInfo.decodeSize;
  174. firstIframe = true;
  175. //PPS
  176. inputBuffer = setBuffer(inputBuffer, PREFIX);
  177. inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(30, 34));
  178. ppsSegment = PAYLOAD.subarray(30, 34);
  179. //I
  180. inputBuffer = setBuffer(inputBuffer, PREFIX);
  181. inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(38, PAYLOAD.length));
  182. } else {
  183. //console.log(newNalHeader[0] & 0x1f)
  184. inputBuffer = setBuffer(inputBuffer, PREFIX);
  185. inputBuffer = setBuffer(inputBuffer, newNalHeader);
  186. inputBuffer = setBuffer(inputBuffer, PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
  187. }
  188. } else {
  189. //console.log(startBit, endBit, 'endBit')
  190. inputBuffer = setBuffer(inputBuffer,
  191. PAYLOAD.subarray(payloadStartIndex, PAYLOAD.length));
  192. }
  193. //console.log(startBit,endBit)
  194. // if(endBit === true) {
  195. // end = true;
  196. // }
  197. break;
  198. case 1:
  199. inputBuffer = setBuffer(inputBuffer, PREFIX);
  200. inputBuffer = setBuffer(inputBuffer, PAYLOAD);
  201. break;
  202. default:
  203. //console.log('nalType: ' + nalType);
  204. //console.log(PAYLOAD)
  205. break;
  206. }
  207. //console.log('ppsSegment: ', ppsSegment)
  208. //console.log('nalType: ' + nalType);
  209. //console.log('M: ' + RtpHeader.M + ' ' + (rtpHeader[1] & 0x80))
  210. //console.log(RtpHeader)
  211. //console.log('RtpHeader.M: ', RtpHeader.M)
  212. //check marker bit
  213. if (RtpHeader.M) {
  214. if (!firstIframe) {
  215. inputLength = 0;
  216. return;
  217. }
  218. let inputBufferSub = inputBuffer.subarray(0, inputLength);
  219. frameType = FRAMETYPE[inputBufferSub[4] & 0x1f];
  220. //只根据视频帧计算resetTimeCount
  221. if(frameType !== 'SEI') {
  222. // rtp时间戳周期为RESETTIME,如果单向递增,设为0
  223. if(lastTimeStamp - rtpTimeStamp > (RESETTIME / 2)) { //判断lastTimeStamp远大于rtpTimeStamp,防止后一帧比前一帧先到的情况
  224. //console.log(lastTimeStamp - rtpTimeStamp)
  225. console.warn('时间戳重置', lastTimeStamp, rtpTimeStamp, frameType, new Date())
  226. resetTimeCount ++;
  227. }
  228. rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
  229. } else {
  230. //同一帧的SEI比视频发送慢时
  231. if (rtpTimeStamp - lastTimeStamp > (RESETTIME / 2)) {
  232. console.warn('SEI翻转', rtpTimeStamp , lastTimeStamp);
  233. rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount - 1);
  234. } else {
  235. rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
  236. }
  237. //console.log('SEI', rtpTimeStamp, 'video', lastTimeStamp, lastTimeStamp+ RESETTIME * resetTimeCount - rtpTimeStamp)
  238. // //同一帧的SEI比视频发送快时
  239. // if(rtpTimeStamp - lastTimeStamp === 3600) {
  240. // if(rtpTimeStamp === 0 ) {
  241. // rtpTimeStamp = rtpTimeStamp + RESETTIME * (resetTimeCount + 1);
  242. // } else {
  243. // rtpTimeStamp = rtpTimeStamp + RESETTIME * resetTimeCount;
  244. // }
  245. // }
  246. }
  247. if(frameType === 'SEI') {
  248. //SEI被分片(nal === 28)时,分片包发送完后marker为1,不会和视频帧一起
  249. SEIBuffer = inputBuffer.subarray(4, inputLength);
  250. //console.log(SEIBuffer)
  251. inputBufferSub = new Uint8Array();
  252. }
  253. if(SEIBuffer) {
  254. let SEI = SEIParse(SEIBuffer);
  255. if (SEI) {
  256. SEIInfo.ivs = SEI;
  257. SEIInfo.timestamp = rtpTimeStamp;
  258. //console.log('GET SEI', rtpTimeStamp)
  259. //SEI信息
  260. decodedData.SEIInfo = SEIInfo;
  261. if (!(rtpTimeStamp - lastTime)) {
  262. console.log('上个SET包时间和本次相等');
  263. console.log('lastTime: ', lastTime, ' rtpTimeStamp: ', rtpTimeStamp, RtpHeader.timeStamp, rtpHeader[4], rtpHeader[5], rtpHeader[6], rtpHeader[7])
  264. }
  265. if ((rtpTimeStamp - lastTime) !== 3600) {
  266. //console.log('SEI 时间差:', (rtpTimeStamp - lastTime), rtpTimeStamp, lastTime)
  267. }
  268. lastTime = rtpTimeStamp;
  269. SEIInfo = {
  270. ivs: null,
  271. timestamp: 0,
  272. };
  273. }
  274. SEIBuffer = null;
  275. }
  276. if(decodeMode === 'canvas' && (frameType !== 'SEI')) {
  277. if (outputSize !== curSize) {
  278. outputSize = curSize;
  279. decoder.setOutputSize(curSize);
  280. }
  281. decodedData.frameData = decoder.decode(inputBufferSub);
  282. decodedData.decodeMode = 'canvas';
  283. decodedData.timeStamp = rtpTimeStamp;
  284. decodedData.width = width;
  285. decodedData.height = height;
  286. this.handleDecodedData(decodedData)
  287. } else {
  288. if (!initalSegmentFlag) {
  289. decodedData.initSegmentData = null;
  290. decodedData.codecInfo = null;
  291. } else {
  292. initalSegmentFlag = false;
  293. const info = {
  294. id: 1,
  295. width: width,
  296. height: height,
  297. type: "video",
  298. profileIdc: SPSParser.getSpsValue("profile_idc"),
  299. profileCompatibility: 0,
  300. levelIdc: SPSParser.getSpsValue("level_idc"),
  301. sps: [spsSegment],
  302. pps: [ppsSegment],
  303. timescale: 1e3,
  304. fps: frameRate
  305. };
  306. decodedData.initSegmentData = info;
  307. decodedData.codecInfo = SPSParser.getCodecInfo();
  308. //console.log(info.pps)
  309. }
  310. if (frameType === 'I') {
  311. //console.log('ppsSegment: ', ppsSegment)
  312. let h264parameterLength = spsSegment.length + ppsSegment.length + 8;
  313. inputSegBufferSub = inputBufferSub.subarray(h264parameterLength, inputBufferSub.length);
  314. } else {
  315. inputSegBufferSub = inputBufferSub.subarray(0, inputBufferSub.length);
  316. }
  317. let segSize = inputSegBufferSub.length - 4;
  318. //mp4 box头
  319. inputSegBufferSub[0] = (segSize & 0xFF000000) >>> 24;
  320. inputSegBufferSub[1] = (segSize & 0xFF0000) >>> 16;
  321. inputSegBufferSub[2] = (segSize & 0xFF00) >>> 8;
  322. inputSegBufferSub[3] = (segSize & 0xFF);
  323. decodedData.frameData = new Uint8Array(inputSegBufferSub);
  324. let sample = {
  325. duration: Math.round((1 / frameRate) * 1000),
  326. size: inputSegBufferSub.length,
  327. frame_time_stamp: null,
  328. frameDuration: null,
  329. };
  330. sample.frame_time_stamp = rtpTimeStamp; //Todo:暂时为null,通过帧率控制duration
  331. if (initalMediaFrameFlag) {
  332. sample.frameDuration = 0;
  333. initalMediaFrameFlag = false;
  334. } else {
  335. if(frameRate) {
  336. sample.frameDuration = Math.round(1000 / frameRate);
  337. }else {
  338. sample.frameDuration = (sample.frame_time_stamp - preSample.frame_time_stamp) / 90; // 时钟频率90000,timescale=1000
  339. }
  340. }
  341. preSample = sample;
  342. decodedData.mediaSample = sample;
  343. decodedData.timeStamp = rtpTimeStamp;
  344. this.handleDecodedData(decodedData);
  345. }
  346. inputLength = 0;
  347. decodedData.SEIInfo = null;
  348. inputSegBufferSub = null;
  349. if(frameType !== 'SEI') {
  350. lastTimeStamp = RtpHeader.timeStamp;
  351. }
  352. frameType = '';
  353. }
  354. //console.log('xxxxxxxxxxxxxxxxxxxxxxxxx')
  355. },
  356. set rtpSessionCallback(func) {
  357. this.handleDecodedData = func;
  358. },
  359. setFrameRate(fps) {
  360. frameRate = fps;
  361. //console.log('frameRate: ', frameRate)
  362. },
  363. setResolutionChangedCallback(callback) {
  364. this.resolutionChangedCallback = callback;
  365. }
  366. }
  367. return new constructor();
  368. function setBuffer(buffer1, buffer2) {
  369. let bufferTemp = buffer1;
  370. if ((inputLength + buffer2.length) > buffer1.length) {
  371. bufferTemp = new Uint8Array(buffer1.length + size1M);
  372. }
  373. bufferTemp.set(buffer2, inputLength);
  374. inputLength += buffer2.length;
  375. return bufferTemp;
  376. }
  377. }
  378. /**
  379. * 去除SPS中的Emulation字节
  380. * @param data SPS源数据
  381. * @returns {Array} 去除后Emulation字节后的SPS
  382. */
  383. function removeH264or5EmulationBytes(data) {
  384. let toSize = 0;
  385. let i = 0;
  386. let to = [];
  387. let dataLength = data.length;
  388. while (i < dataLength) {
  389. if (i + 2 < dataLength && data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 3) {
  390. to[toSize] = to[toSize + 1] = 0;
  391. toSize += 2;
  392. i += 3;
  393. } else {
  394. to[toSize] = data[i];
  395. toSize += 1;
  396. i += 1;
  397. }
  398. }
  399. return to;
  400. }
  401. /**
  402. * 解析SEI信息
  403. * @param data
  404. * @return {Array}
  405. */
  406. function SEIParse(data) {
  407. //console.log(data)
  408. if((data[0] & 0x1f) !== 6) {
  409. //非SEI
  410. return null;
  411. }
  412. if(data[1] !== 0x55 || data[2] !== 0x4C ||data[3] !== 0x53 ||data[4] !== 0x40) {
  413. //ULS@开头
  414. console.warn('unknown SEI type');
  415. return null;
  416. }
  417. let dataLength = data.length;
  418. let type = (data[5] << 8) + data[6];
  419. let checkSum = (data[7] << 8) + data[8];
  420. // if(dataLength !== (checkSum + 1 + 4 + 4)) {
  421. // console.log('SEI check fail!');
  422. // return null;
  423. // }
  424. let result;
  425. data = data.subarray(9);
  426. switch(type) {
  427. case 0:
  428. result = parseRect(data);
  429. break;
  430. case 1:
  431. //console.log(parseBody(data))
  432. result = parseBody(data);
  433. break;
  434. default:
  435. result = null;
  436. break;
  437. }
  438. return result;
  439. }
  440. function parseRect(data) {
  441. //console.log(data)
  442. let dataLength = data.length;
  443. let contents = [];
  444. while(dataLength > 0) {
  445. //console.log('dataLength: ', dataLength)
  446. let x0 = ((data[4] & 0x7f) << 8) + data[5],
  447. y0 = ((data[6] & 0x7f) << 8) + data[7],
  448. width = ((data[8] & 0x7f) << 8) + data[9] - x0,
  449. height = ((data[10] & 0x7f) << 8) + data[11] - y0;
  450. let content = {
  451. type: 'rect',
  452. id: (data[2] << 8) + data[3],
  453. rect: [x0, y0, width, height],
  454. state: data[1] & 0x01,
  455. quality: (data[1] & 0x02) >> 1,
  456. };
  457. Array.prototype.push.apply(contents, [content]);
  458. data = data.subarray(12);
  459. dataLength = data.length;
  460. }
  461. return contents;
  462. }
  463. function parseBody(data) {
  464. let dataLength = data.length;
  465. let contents = [];
  466. while(dataLength > 0) {
  467. //console.log('dataLength: ', dataLength)
  468. let x0 = ((data[4] & 0x7f) << 8) + data[5],
  469. y0 = ((data[6] & 0x7f) << 8) + data[7],
  470. width = ((data[8] & 0x7f) << 8) + data[9] - x0,
  471. height = ((data[10] & 0x7f) << 8) + data[11] - y0,
  472. boxConfidence = ((data[12] & 0x7f) << 8) + data[13];
  473. let points = [];
  474. for(let i = 0; i < 17; i++) {
  475. let point = {
  476. x: ((data[16 + i * 8] & 0x7f) << 8) + data[17 + i * 8],
  477. y: ((data[18 + i * 8] & 0x7f) << 8) + data[19 + i * 8],
  478. confidence: ((data[20 + i * 8] & 0x7f) << 8) + data[21 + i * 8],
  479. // x: Math.random() * 8191,
  480. // y: Math.random() * 8191,
  481. // confidence: 1,
  482. };
  483. points.push(point);
  484. }
  485. let content = {
  486. type: 'coco-pose',
  487. id: (data[2] << 8) + data[3],
  488. handsUp: data[1] & 0x04,
  489. boundingBox: [x0, y0, width, height],
  490. boxConfidence,
  491. points: parseBodyToTree(points),
  492. state: data[1] & 0x01,
  493. };
  494. Array.prototype.push.apply(contents, [content]);
  495. data = data.subarray(152);
  496. dataLength = data.length;
  497. }
  498. return contents;
  499. }
  500. /**
  501. * 将智能帧中的人体姿态点转化为树结构(双亲表示法)
  502. * @param points
  503. */
  504. function parseBodyToTree(points) {
  505. let newPoints = [];
  506. newPoints[0] = {...points[0], parent: -1, pointColor:'#FF0002', lineColor: '#FF0002'};
  507. newPoints[1] = {...points[1], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
  508. newPoints[2] = {...points[2], parent: 0, pointColor:'#FF0002', lineColor: '#FF0002'};
  509. newPoints[3] = {...points[5], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
  510. newPoints[4] = {...points[6], parent: 17, pointColor:'#D9E34F', lineColor: '#FF0002'};
  511. newPoints[5] = {...points[3], parent: 1, pointColor:'#00FF00', lineColor: '#FF0002'};
  512. newPoints[6] = {...points[4], parent: 2, pointColor:'#FFAC00', lineColor: '#FF0002'};
  513. newPoints[7] = {...points[7], parent: 3, pointColor:'#00FF45', lineColor: '#'};
  514. newPoints[8] = {...points[11], parent: 17, pointColor:'#EEC446', lineColor: '#'};
  515. newPoints[9] = {...points[8], parent: 4, pointColor:'#43D3AF', lineColor: '#'};
  516. newPoints[10] = {...points[12], parent: 17, pointColor:'#7A93E8', lineColor: '#'};
  517. newPoints[11] = {...points[9], parent: 7, pointColor:'#EFB842', lineColor: '#'};
  518. newPoints[12] = {...points[13], parent: 8, pointColor:'#E56C00', lineColor: '#'};
  519. newPoints[13] = {...points[10], parent: 9, pointColor:'#47CD43', lineColor: '#0096FF'};
  520. newPoints[14] = {...points[14], parent: 10, pointColor:'#3868D2', lineColor: '#00FF51'};
  521. newPoints[15] = {...points[15], parent: 12, pointColor:'#DF4D01', lineColor: '#5100FF'};
  522. newPoints[16] = {...points[16], parent: 14, pointColor:'#1E48D4', lineColor: '#00FFA0'};
  523. newPoints[17] = {x: (points[5].x + points[6].x) / 2,y: (points[5].y + points[6].y) / 2, parent: -1, pointColor:'#D9E34F', lineColor: '#00FFA0'};
  524. return newPoints;
  525. }