NetworkController.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import Codes from "./enum/Codes.js"
  2. import {logger} from "./Logger.js"
  3. import Socket from "./Socket.js"
  4. import Rtcp from "./Rtcp.js"
  5. import NetworkMonitor from "./NetworkMonitor.js"
  6. import Stream from "./Stream.js"
  7. var workerSourceCode = `onmessage = function (event) {
  8. const data = event.data
  9. if (!data) return
  10. if (data.type === 'start') {
  11. const startTime = Date.now()
  12. const request = new XMLHttpRequest()
  13. request.open('GET', data.url)
  14. try {
  15. request.send()
  16. } catch (error) {
  17. console.error(error)
  18. }
  19. request.addEventListener('readystatechange', () => {
  20. if (request.readyState == 4) {
  21. if (request.status == 200) {
  22. postMessage(Date.now() - startTime)
  23. }
  24. }
  25. })
  26. }
  27. }
  28. `;
  29. export default class NetworkController extends EventEmitter {
  30. constructor(e) {
  31. super();
  32. this.socket = null
  33. this.rtcp = null
  34. this.stream = null
  35. this._state = 'connecting'
  36. this._networkMonitor = null
  37. this.blockedActions = []
  38. this.reconnectCount = 0
  39. this.room = e,
  40. this.socket = new Socket(this);
  41. this.rtcp = new Rtcp(this);
  42. this.stream = new Stream;
  43. this._networkMonitor = new NetworkMonitor(()=>{
  44. logger.info("network changed, online:", this._networkMonitor.isOnline),
  45. this._state === "disconnected" && this._networkMonitor.isOnline && (logger.info("network back to online, try to reconnect"),
  46. this.reconnect())
  47. });
  48. this.checkNetworkQuality(this.room.currentNetworkOptions.wsServerUrl);
  49. this._networkMonitor.start();
  50. new VisibilityChangeHandler().subscribe(r=>{
  51. var n, o;
  52. r ? ((o = this.room.stats) == null || o.disable(),
  53. logger.infoAndReportMeasurement({
  54. metric: "pageHide",
  55. startTime: Date.now()
  56. })) : ((n = this.room.stats) == null || n.enable(),
  57. logger.infoAndReportMeasurement({
  58. metric: "pageShow",
  59. startTime: Date.now(),
  60. extra: {
  61. state: this._state
  62. }
  63. }),
  64. this._state === "disconnected" && this.reconnect())
  65. }
  66. )
  67. }
  68. startGame(){
  69. return new Promise((e,t)=>{
  70. if (!this.rtcp.connected)
  71. return t(new InternalError("Game cannot load. Please refresh"));
  72. if (!this.rtcp.inputReady)
  73. return t(new InternalError("Game is not ready yet. Please wait"));
  74. this.socket.on("gameRoomAvailable", r=>{
  75. this.setState("connected"),
  76. e(r),
  77. this.rtcp.heartbeat.start()
  78. }
  79. ),
  80. this.socket.on("socketClosed", r=>{
  81. t(r)
  82. }
  83. ),
  84. this.socket.startGame()
  85. })
  86. }
  87. addBlockedActions(e) {
  88. this.blockedActions.push(...e)
  89. }
  90. removeBlockedActions(e) {
  91. if (!e) {
  92. this.blockedActions = [];
  93. return
  94. }
  95. const t = this.blockedActions.indexOf(e);
  96. this.blockedActions.splice(t, 1)
  97. }
  98. setState(e) {
  99. this._state !== e && (logger.info("Set network state to ", e),
  100. this._state = e)
  101. }
  102. async connectAndStart(e) {
  103. return this.connect(e).then(this.startGame)
  104. }
  105. async connect(e=!1) {
  106. this.room.updateCurrentNetworkOptions({
  107. reconnect: e
  108. });
  109. return new Promise((t,r)=>{
  110. this.rtcp.on("rtcConnected", ()=>{
  111. this.setState("connected"),
  112. t()
  113. }
  114. ),
  115. this.rtcp.on("rtcDisconnected", ()=>{
  116. logger.info("rtc disconnected"),
  117. this._state === "connecting" ? (this.setState("disconnected"),
  118. r(new InternalError("rtc connect failed"))) : (this.setState("disconnected"),
  119. logger.info("rtc disconnected, start to reconnect"),
  120. this.reconnect())
  121. }
  122. ),
  123. this.socket.on("socketQuit", ()=>{
  124. logger.info("socket quit success"),
  125. this.setState("closed")
  126. }
  127. ),
  128. this.socket.on("socketClosed", n=>{
  129. this._state === "connecting" && (this.setState("disconnected"),
  130. r(n)),
  131. r(n)
  132. }
  133. ),
  134. this.socket.start()
  135. }
  136. )
  137. }
  138. reconnect() {
  139. if (this.room.viewMode === "observer")
  140. return;
  141. const e = Date.now();
  142. if (this.reconnectCount++,
  143. this.reconnectCount > MAX_RECONNECT_COUNT) {
  144. logger.error("reconnect failed, reached max reconnect count", MAX_RECONNECT_COUNT),
  145. this.reconnectCount = 0,
  146. this.emit("stateChanged", {
  147. state: "disconnected"
  148. });
  149. return
  150. }
  151. return logger.info("start reconnect, count:", this.reconnectCount),
  152. this._reconnect().then(()=>{
  153. logger.infoAndReportMeasurement({
  154. startTime: e,
  155. metric: "reconnect"
  156. })
  157. }
  158. ).catch(t=>{
  159. if (logger.infoAndReportMeasurement({
  160. startTime: e,
  161. metric: "reconnect",
  162. error: t
  163. }),
  164. t.code === Codes.RepeatLogin) {
  165. this.room.handleRepetLogin();
  166. return
  167. }
  168. const r = 1e3;
  169. logger.info("reconnect failed, wait " + r + " ms for next reconnect"),
  170. setTimeout(()=>{
  171. this.reconnect()
  172. }
  173. , r)
  174. }
  175. )
  176. }
  177. _reconnect() {
  178. return this._state === "closed" ? (logger.warn("connection closed already"),
  179. Promise.reject()) : this._state === "connecting" ? (logger.warn("connection is already in connecting state"),
  180. Promise.reject()) : this._state !== "disconnected" ? Promise.reject() : (this.prepareReconnect(),
  181. this._state = "connecting",
  182. this.emit("stateChanged", {
  183. state: "reconnecting",
  184. count: this.reconnectCount
  185. }),
  186. this.socket.off("gameRoomAvailable"),
  187. this.socket.off("socketClosed"),
  188. this.rtcp.off("rtcDisconnected"),
  189. this.rtcp.off("rtcConnected"),
  190. this.connectAndStart(!0).then(({session_id: e})=>{
  191. this.room.updateCurrentNetworkOptions({
  192. sessionId: e
  193. }),
  194. reporter.updateBody({
  195. serverSession: e
  196. }),
  197. logger.info("reconnect success"),
  198. this.setState("connected"),
  199. this.reconnectCount = 0,
  200. this.emit("stateChanged", {
  201. state: "reconnected"
  202. })
  203. }
  204. ))
  205. }
  206. prepareReconnect() {
  207. this.rtcp.disconnect(),
  208. this.socket.prepareReconnect(),
  209. this.prepareReconnectOptions()
  210. }
  211. prepareReconnectOptions() {
  212. const {camera: e, player: t} = this.room.currentClickingState || {};
  213. e && t && this.room.updateCurrentNetworkOptions({
  214. camera: e,
  215. player: t
  216. })
  217. }
  218. sendRtcData(e) {
  219. if (this.blockedActions.includes(e.action_type)) {
  220. logger.info(`action: ${Actions[e.action_type]} was blocked`);
  221. return
  222. }
  223. this.rtcp.sendData(e)
  224. }
  225. sendSocketData(e) {
  226. logger.debug("ws send ->", e),
  227. this.socket.send(e)
  228. }
  229. quit() {
  230. const e = uuid$1()
  231. , t = {
  232. action_type: Actions.Exit,
  233. trace_id: e,
  234. exit_action: {},
  235. user_id: this.room.options.userId,
  236. packet_id: e
  237. };
  238. this.setState("closed"),
  239. this.socket.quit(),
  240. this.sendRtcData(t)
  241. }
  242. checkNetworkQuality(i) {
  243. let worker = null
  244. if (!i)
  245. return;
  246. const e = Date.now();
  247. if (this.pingOthers("https://www.baidu.com", function(t, r) {
  248. logger.infoAndReportMeasurement({
  249. metric: "baiduRtt",
  250. group: "http",
  251. value: r,
  252. startTime: e
  253. })
  254. }),
  255. !worker) {
  256. const t = new Blob([workerSourceCode],{
  257. type: "application/javascript"
  258. });
  259. worker = new Worker(URL.createObjectURL(t)),
  260. worker.onmessage = function(r) {
  261. logger.infoAndReportMeasurement({
  262. metric: "workerRtt",
  263. group: "http",
  264. startTime: e,
  265. value: r.data
  266. })
  267. }
  268. }
  269. }
  270. pingOthers(i, e) {
  271. let t = !1;
  272. const r = new Image;
  273. r.onload = o,
  274. r.onerror = a;
  275. const n = Date.now();
  276. function o(l) {
  277. t = !0,
  278. s()
  279. }
  280. function a(l) {}
  281. function s() {
  282. const l = Date.now() - n;
  283. if (typeof e == "function")
  284. return t ? e(null, l) : (console.error("error loading resource"),
  285. e("error", l))
  286. }
  287. r.src = i + "/favicon.ico?" + +new Date
  288. }
  289. }