Repository: flutter
Issue: [video_player] [web] listener on video player controller is continuously called, even when video ended
Labels: platform-web,p: video_player,package,has reproducible steps,P2,found in release: 3.0,found in release: 3.1,team-web,triaged-web

When adding a listener to a `VideoPlayerController`, on the web, the listener is continuously fired even after the video itself ended.

I had a deeper look since the underlying ChangeNotifier should have blocked duplicate events due to the previous event being equal to the next (when the video ends).

Turns out that `VideoPlayerValue` does not override `operator==` and `hashCode`

## Steps to Reproduce

1. flutter create bug
2. update main.dart with the code sample
3. add `video_player: ^2.4.5` to dependencies
4. flutter run -d chrome
5. wait until the video loaded
6. press the button to play the video
7. let the video play until the end of the video
8. `video ended` is printed when the video ends (expected)
9. `video ended` is being printed even after the first 'end' event was delivered to the listener 

**Expected results:** The listener prints `video ended` exactly once, when the video ended

**Actual results:** `video ended` keeps being printed endlessly

<details>
<summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final VideoPlayerController _controller;

  void _listener() {
    final value = _controller.value;

    if (!value.isInitialized || value.errorDescription != null) {
      return;
    }

    if (value.position == value.duration) {
      print('video ended');
    }
  }

  @override
  void initState() {
    super.initState();
    _controller = VideoPlayerController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
    );

    _controller.addListener(_listener);
    _controller.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          final value = _controller.value;

          if (!value.isInitialized) {
            return const Center(child: Text('loading'));
          }

          final error = value.errorDescription;

          if (error != null) {
            return Center(child: Text(error));
          }

          return Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  ElevatedButton(
                    onPressed: () async {
                      if (value.isPlaying) {
                        await _controller.pause();
                      } else {
                        await _controller.play();
                      }
                    },
                    child: value.isPlaying
                        ? const Text('Pause video')
                        : const Text('Play video'),
                  ),
                ],
              ),
              Expanded(
                child: Center(
                  child: AspectRatio(
                    aspectRatio: value.aspectRatio,
                    child: VideoPlayer(_controller),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.removeListener(_listener);
    _controller.dispose();
    super.dispose();
  }
}
```

</details>

<details>
  <summary>Logs</summary>

```
[✓] Flutter (Channel stable, 3.0.2, on macOS 12.2.1 21D62 darwin-x64, locale
    en-BE)
    • Flutter version 3.0.2 at /Users/navaronbracke/Documents/flutter
    • Upstream repository git@github.com:flutter/flutter.git
    • Framework revision cd41fdd495 (5 weeks ago), 2022-06-08 09:52:13 -0700
    • Engine revision f15f824b57
    • Dart version 2.17.3
    • DevTools version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/navaronbracke/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • ANDROID_HOME = /Users/navaronbracke/Library/Android/sdk
    • Java binary at: /Applications/Android
      Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build
      11.0.12+0-b1504.28-7817840)

[✓] VS Code (version 1.69.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.44.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 12.2.1 21D62 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 103.0.5060.114

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

```

</details>

[video of issue](https://drive.google.com/file/d/1cHn2zeS1fsH8qizgTP6zeI3dbtUs66Uz/view?usp=sharing)